diff --git a/.all-contributorsrc b/.all-contributorsrc
index 06900bf5..d8833825 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -744,6 +744,15 @@
"contributions": [
"bug"
]
+ },
+ {
+ "login": "johnraz",
+ "name": "Jonathan Liuti",
+ "avatar_url": "https://avatars.githubusercontent.com/u/304164?v=4",
+ "profile": "https://github.com/johnraz",
+ "contributions": [
+ "bug"
+ ]
}
],
"contributorsPerLine": 7,
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index b53fba89..a53abae4 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -47,58 +47,59 @@ contributions that have helped to improve requests-cache:
Jessy Williams π» π β οΈ |
John Vandenberg π π¦ β οΈ |
+ Jonathan Liuti π |
JonΓ‘Ε‘ JanΔaΕΓk π |
Jordan Cook π» π§ β¨ π β οΈ π π |
JΓΌrgen Hermann π π€ |
Kieran W π π |
- MHellmund π |
+ MHellmund π |
Manuel Eggimann π π» |
Marc Abramowitz π» π |
Marius Gedminas π» π |
Masahiro Wada π» β¨ |
Massimo Santini π€ |
Michael Herman π» π |
- MichaΕ GΓ³rny π |
+ MichaΕ GΓ³rny π |
MichaΕ Nowotka π€ |
Mike π» β¨ |
Nate π |
Nathan Cahill π |
Nick π€ |
Olivier Dalang π» |
- Parker Hancock π» β¨ π β οΈ π π‘οΈ π€ |
+ Parker Hancock π» β¨ π β οΈ π π‘οΈ π€ |
Philipp A. π |
Roderic Day π |
Roman Haritonov π» π§ β¨ π β οΈ π π |
Samuel T. π π€ |
Sebastian HΓΆffner π» β¨ β οΈ π€ |
Serhii Chvaliuk π π» |
- Simon Biewald π‘οΈ π€ |
+ Simon Biewald π‘οΈ π€ |
Skipper Seabold π |
Slin Lee π |
Stavros Korokithakis π π§ π |
Taher Chegini π |
Vladimir Panteleev π€ |
Willem de Groot π» π |
- Wouter Vanden Hove π |
+ Wouter Vanden Hove π |
YetAnotherNerd π» β¨ π |
aaron-mf1 π€ |
coryairbhb π |
craig π» π |
denis-bz π |
girst π |
- gorogoroumaru π» |
+ gorogoroumaru π» |
harvey251 π |
mbarkhau π» β οΈ π π |
shiftinv π» π |
diff --git a/HISTORY.md b/HISTORY.md
index b3e9a0c4..1d80b308 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -1,8 +1,9 @@
# History
-## 0.9.2 (Unreleased)
-* Support `params`, `data`, and `json` as positional arguments to `CachedSession.request()` (fixes
- regression in `0.9.0`)
+## 0.9.2 (2022-02-15)
+Fix some regression bugs introduced in 0.9.0:
+* Add support `params` as a positional argument to `CachedSession.request()`
+* Add support for disabling expiration for a single request with `CachedSession.request(..., expire_after=-1)`
## 0.9.1 (2022-01-15)
* Add support for python 3.10.2 and 3.9.10 (regarding resolving `ForwardRef` types during deserialization)
diff --git a/docs/user_guide/expiration.md b/docs/user_guide/expiration.md
index 37bc010c..3113604d 100644
--- a/docs/user_guide/expiration.md
+++ b/docs/user_guide/expiration.md
@@ -75,10 +75,10 @@ retrieving a new response. If you would like to use expired response data in the
For example:
```python
->>> # Cache a test response that will expire immediately
+>>> # Cache a test response and wait until it's expired
>>> session = CachedSession(stale_if_error=True)
->>> session.get('https://httpbin.org/get', expire_after=0.0001)
->>> time.sleep(0.0001)
+>>> session.get('https://httpbin.org/get', expire_after=1)
+>>> time.sleep(1)
```
Afterward, let's say the page has moved and you get a 404, or the site is experiencing downtime and
diff --git a/requests_cache/cache_control.py b/requests_cache/cache_control.py
index d7d6f2ca..81fbcace 100644
--- a/requests_cache/cache_control.py
+++ b/requests_cache/cache_control.py
@@ -27,8 +27,9 @@
__all__ = ['DO_NOT_CACHE', 'CacheActions']
-# May be set by either headers or expire_after param to disable caching
+# May be set by either headers or expire_after param to disable caching or disable expiration
DO_NOT_CACHE = 0
+NEVER_EXPIRE = -1
# Supported Cache-Control directives
CACHE_DIRECTIVES = ['immutable', 'max-age', 'no-cache', 'no-store']
@@ -139,7 +140,7 @@ def update_from_response(self, response: Response):
# Check headers for expiration, validators, and other cache directives
if directives.get('immutable'):
- self.expire_after = -1
+ self.expire_after = NEVER_EXPIRE
else:
self.expire_after = coalesce(
directives.get('max-age'), directives.get('expires'), self.expire_after
@@ -156,7 +157,7 @@ def update_from_response(self, response: Response):
def get_expiration_datetime(expire_after: ExpirationTime) -> Optional[datetime]:
"""Convert an expiration value in any supported format to an absolute datetime"""
# Never expire
- if expire_after is None or expire_after == -1:
+ if expire_after is None or expire_after == NEVER_EXPIRE:
return None
# Expire immediately
elif try_int(expire_after) == DO_NOT_CACHE:
@@ -173,10 +174,10 @@ def get_expiration_datetime(expire_after: ExpirationTime) -> Optional[datetime]:
return datetime.utcnow() + expire_after
-def get_expiration_seconds(expire_after: ExpirationTime) -> Optional[int]:
+def get_expiration_seconds(expire_after: ExpirationTime) -> int:
"""Convert an expiration value in any supported format to an expiration time in seconds"""
expires = get_expiration_datetime(expire_after)
- return ceil((expires - datetime.utcnow()).total_seconds()) if expires else None
+ return ceil((expires - datetime.utcnow()).total_seconds()) if expires else NEVER_EXPIRE
def get_cache_directives(headers: Mapping) -> Dict:
@@ -242,7 +243,10 @@ def to_utc(dt: datetime):
def try_int(value: Any) -> Optional[int]:
"""Convert a value to an int, if possible, otherwise ``None``"""
- return int(str(value)) if str(value).isnumeric() else None
+ try:
+ return int(value)
+ except (TypeError, ValueError):
+ return None
def url_match(url: str, pattern: str) -> bool:
diff --git a/tests/unit/test_session.py b/tests/unit/test_session.py
index 9fc5df0d..626231d8 100644
--- a/tests/unit/test_session.py
+++ b/tests/unit/test_session.py
@@ -10,7 +10,7 @@
import pytest
import requests
-from requests import Request
+from requests import Request, RequestException
from requests.structures import CaseInsensitiveDict
from requests_cache import ALL_METHODS, CachedResponse, CachedSession
@@ -154,12 +154,12 @@ def test_response_history(mock_session):
def test_repr(mock_session):
"""Test session and cache string representations"""
- mock_session.expire_after = 10.5
+ mock_session.expire_after = 11
mock_session.cache.responses['key'] = 'value'
mock_session.cache.redirects['key'] = 'value'
mock_session.cache.redirects['key_2'] = 'value'
- assert mock_session.cache.cache_name in repr(mock_session) and '10.5' in repr(mock_session)
+ assert mock_session.cache.cache_name in repr(mock_session) and '11' in repr(mock_session)
assert '2 redirects' in str(mock_session.cache) and '1 responses' in str(mock_session.cache)
@@ -515,9 +515,9 @@ def test_expired_request_error(mock_session):
"""Without stale_if_error (default), if there is an error while re-fetching an expired
response, the request should be re-raised and the expired item deleted"""
mock_session.stale_if_error = False
- mock_session.expire_after = 0.01
+ mock_session.expire_after = 1
mock_session.get(MOCKED_URL)
- time.sleep(0.01)
+ time.sleep(1)
with patch.object(mock_session.cache, 'save_response', side_effect=ValueError):
with pytest.raises(ValueError):
@@ -528,12 +528,12 @@ def test_expired_request_error(mock_session):
def test_stale_if_error__exception(mock_session):
"""With stale_if_error, expect to get old cache data if there is an exception during a request"""
mock_session.stale_if_error = True
- mock_session.expire_after = 0.2
+ mock_session.expire_after = 1
assert mock_session.get(MOCKED_URL).from_cache is False
assert mock_session.get(MOCKED_URL).from_cache is True
- time.sleep(0.2)
- with patch.object(mock_session.cache, 'save_response', side_effect=ValueError):
+ time.sleep(1)
+ with patch.object(mock_session.cache, 'save_response', side_effect=RequestException):
response = mock_session.get(MOCKED_URL)
assert response.from_cache is True and response.is_expired is True
@@ -541,12 +541,12 @@ def test_stale_if_error__exception(mock_session):
def test_stale_if_error__error_code(mock_session):
"""With stale_if_error, expect to get old cache data if a response has an error status code"""
mock_session.stale_if_error = True
- mock_session.expire_after = 0.2
+ mock_session.expire_after = 1
mock_session.allowable_codes = (200, 404)
assert mock_session.get(MOCKED_URL_404).from_cache is False
- time.sleep(0.2)
+ time.sleep(1)
response = mock_session.get(MOCKED_URL_404)
assert response.from_cache is True and response.is_expired is True
@@ -718,8 +718,8 @@ def test_remove_expired_responses__per_request(mock_session):
assert len(mock_session.cache.responses) == 1
-def test_per_request__expiration(mock_session):
- """No per-session expiration is set, but then overridden with per-request expiration"""
+def test_per_request__enable_expiration(mock_session):
+ """No per-session expiration is set, but then overridden for a single request"""
mock_session.expire_after = None
response = mock_session.get(MOCKED_URL, expire_after=1)
assert response.from_cache is False
@@ -730,6 +730,15 @@ def test_per_request__expiration(mock_session):
assert response.from_cache is False
+def test_per_request__disable_expiration(mock_session):
+ """A per-session expiration is set, but then disabled for a single request"""
+ mock_session.expire_after = 60
+ response = mock_session.get(MOCKED_URL, expire_after=-1)
+ response = mock_session.get(MOCKED_URL, expire_after=-1)
+ assert response.from_cache is True
+ assert response.expires is None
+
+
def test_per_request__prepared_request(mock_session):
"""The same should work for PreparedRequests with CachedSession.send()"""
mock_session.expire_after = None