summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJordan Cook <jordan.cook@pioneer.com>2022-05-03 14:52:37 -0500
committerJordan Cook <jordan.cook@pioneer.com>2022-05-03 14:52:37 -0500
commit7f4c20525770d463c5526fa601e8e3b5472cf2e6 (patch)
tree98bf214cb4ad9395f136a6a93010f0ada54e3a20
parenta300510c4ba087fd41d31c379a9d03e3b80b55aa (diff)
downloadrequests-cache-7f4c20525770d463c5526fa601e8e3b5472cf2e6.tar.gz
Add always_revalidate session option
-rw-r--r--docs/user_guide/expiration.md9
-rwxr-xr-xrequests_cache/models/response.py4
-rw-r--r--requests_cache/policy/actions.py3
-rw-r--r--requests_cache/policy/settings.py1
-rw-r--r--requests_cache/session.py4
-rw-r--r--tests/unit/test_session.py26
6 files changed, 41 insertions, 6 deletions
diff --git a/docs/user_guide/expiration.md b/docs/user_guide/expiration.md
index a53da87..3257571 100644
--- a/docs/user_guide/expiration.md
+++ b/docs/user_guide/expiration.md
@@ -181,6 +181,15 @@ Example:
>>> assert response_2.from_cache is False
```
+### Validation-Only Requests
+If you want to always send a conditional request before using a cached response, you can use the
+session setting `always_revalidate`:
+```python
+>>> session = CachedSession(always_revalidate=True)
+```
+
+Unlike the `refresh` option, this only affects cached responses with a validator.
+
### Cache-Only Requests
If you want to only use cached responses without making any real requests, you can use the
`only_if_cached` option. This essentially uses your cache in "offline mode". If a response isn't
diff --git a/requests_cache/models/response.py b/requests_cache/models/response.py
index 7d40998..09c7e8c 100755
--- a/requests_cache/models/response.py
+++ b/requests_cache/models/response.py
@@ -28,9 +28,10 @@ class BaseResponse(Response):
provide type hints for extra cache-related attributes that are added to non-cached responses.
"""
- cache_key: Optional[str] = None
created_at: datetime = field(factory=datetime.utcnow)
expires: Optional[datetime] = field(default=None)
+ cache_key: Optional[str] = None # Not serialized; set by BaseCache.get_response()
+ revalidated: bool = False # Not serialized; set by CacheActions.update_revalidated_response()
@property
def from_cache(self) -> bool:
@@ -63,7 +64,6 @@ class CachedResponse(BaseResponse, RichMixin):
_content: bytes = field(default=None)
_next: Optional[CachedRequest] = field(default=None)
- cache_key: Optional[str] = None # Not serialized; set by BaseCache.get_response()
cookies: RequestsCookieJar = field(factory=RequestsCookieJar)
created_at: datetime = field(factory=datetime.utcnow)
elapsed: timedelta = field(factory=timedelta)
diff --git a/requests_cache/policy/actions.py b/requests_cache/policy/actions.py
index fd61507..cdacefd 100644
--- a/requests_cache/policy/actions.py
+++ b/requests_cache/policy/actions.py
@@ -203,6 +203,7 @@ class CacheActions(RichMixin):
logger.debug(f'Response for URL {response.request.url} has not been modified')
cached_response.expires = self.expires
cached_response.headers.update(response.headers)
+ cached_response.revalidated = True
return cached_response
def _update_from_response_headers(self, directives: CacheDirectives):
@@ -230,7 +231,7 @@ class CacheActions(RichMixin):
or self._refresh
or directives.no_cache
or directives.must_revalidate
- and directives.max_age == 0
+ or (self._settings.always_revalidate and directives.has_validator)
)
# Add the appropriate validation headers, if needed
diff --git a/requests_cache/policy/settings.py b/requests_cache/policy/settings.py
index 7888e45..2f9b9fe 100644
--- a/requests_cache/policy/settings.py
+++ b/requests_cache/policy/settings.py
@@ -25,6 +25,7 @@ class CacheSettings(RichMixin):
allowable_codes: Iterable[int] = field(default=DEFAULT_STATUS_CODES)
allowable_methods: Iterable[str] = field(default=DEFAULT_METHODS)
+ always_revalidate: bool = field(default=None)
cache_control: bool = field(default=False)
disabled: bool = field(default=False)
expire_after: ExpirationTime = field(default=None)
diff --git a/requests_cache/session.py b/requests_cache/session.py
index de0a0cd..58b35ea 100644
--- a/requests_cache/session.py
+++ b/requests_cache/session.py
@@ -51,6 +51,7 @@ class CacheMixin(MIXIN_BASE):
cache_control: bool = False,
allowable_codes: Iterable[int] = DEFAULT_STATUS_CODES,
allowable_methods: Iterable[str] = DEFAULT_METHODS,
+ always_revalidate: bool = False,
ignored_parameters: Iterable[str] = DEFAULT_IGNORED_PARAMS,
match_headers: Union[Iterable[str], bool] = False,
filter_fn: FilterCallback = None,
@@ -65,6 +66,7 @@ class CacheMixin(MIXIN_BASE):
cache_control=cache_control,
allowable_codes=allowable_codes,
allowable_methods=allowable_methods,
+ always_revalidate=always_revalidate,
ignored_parameters=ignored_parameters,
match_headers=match_headers,
filter_fn=filter_fn,
@@ -326,6 +328,8 @@ class CachedSession(CacheMixin, OriginalSession):
cache_control: Use Cache-Control and other response headers to set expiration
allowable_codes: Only cache responses with one of these status codes
allowable_methods: Cache only responses for one of these HTTP methods
+ always_revalidate: Revalidate with the server for every request, even if the cached response
+ is not expired
match_headers: Match request headers when reading from the cache; may be either ``True`` or
a list of specific headers to match
ignored_parameters: Request paramters, headers, and/or JSON body params to exclude from both
diff --git a/tests/unit/test_session.py b/tests/unit/test_session.py
index ad2ecbd..6e37ded 100644
--- a/tests/unit/test_session.py
+++ b/tests/unit/test_session.py
@@ -801,8 +801,28 @@ def test_request_refresh(mock_session):
assert response_1.from_cache is False
assert response_2.from_cache is True
- assert response_3.from_cache is True
- assert response_4.from_cache is True
+ assert response_3.from_cache is True and response_3.revalidated is True
+ assert response_4.from_cache is True and response_4.revalidated is False
+
+ # Expect expiration to get reset after revalidation
+ assert response_2.expires < response_4.expires
+
+
+def test_request_always_revalidate(mock_session):
+ """The session always_revalidate option should send a conditional request, if possible"""
+ mock_session.settings.expire_after = 60
+ response_1 = mock_session.get(MOCKED_URL_ETAG)
+ response_2 = mock_session.get(MOCKED_URL_ETAG)
+ mock_session.mock_adapter.register_uri('GET', MOCKED_URL_ETAG, status_code=304)
+
+ mock_session.settings.always_revalidate = True
+ response_3 = mock_session.get(MOCKED_URL_ETAG)
+ response_4 = mock_session.get(MOCKED_URL_ETAG)
+
+ assert response_1.from_cache is False
+ assert response_2.from_cache is True
+ assert response_3.from_cache is True and response_3.revalidated is True
+ assert response_4.from_cache is True and response_4.revalidated is True
# Expect expiration to get reset after revalidation
assert response_2.expires < response_4.expires
@@ -821,7 +841,7 @@ def test_request_refresh__no_validator(mock_session):
assert response_1.from_cache is False
assert response_2.from_cache is True
- assert response_3.from_cache is True
+ assert response_3.from_cache is True and response_3.revalidated is False
assert response_2.expires == response_4.expires