summaryrefslogtreecommitdiff
path: root/requests_cache/cache_control.py
diff options
context:
space:
mode:
authorJordan Cook <jordan.cook@pioneer.com>2021-09-13 17:38:12 -0500
committerJordan Cook <jordan.cook@pioneer.com>2021-09-18 17:43:12 -0500
commitdbee4da8293e8410deeb77ddb5c2a6f5cc4c31eb (patch)
tree8b0cdebf6f6d68b96f9e158dfa43df1813086455 /requests_cache/cache_control.py
parent1fa0fbb715e5964eb8aee467721f6b5a0f4cfdb6 (diff)
downloadrequests-cache-dbee4da8293e8410deeb77ddb5c2a6f5cc4c31eb.tar.gz
Make per-request expiration thread-safe by passing via request headers instead of session attribute, and use Cache-Control request headers by default
Diffstat (limited to 'requests_cache/cache_control.py')
-rw-r--r--requests_cache/cache_control.py71
1 files changed, 25 insertions, 46 deletions
diff --git a/requests_cache/cache_control.py b/requests_cache/cache_control.py
index b3d1703..79add63 100644
--- a/requests_cache/cache_control.py
+++ b/requests_cache/cache_control.py
@@ -47,7 +47,7 @@ class CacheActions:
If multiple sources provide an expiration time, they will be used in the following order of
precedence:
- 1. Cache-Control request headers (if enabled)
+ 1. Cache-Control request headers
2. Cache-Control response headers (if enabled)
3. Per-request expiration
4. Per-URL expiration
@@ -67,53 +67,26 @@ class CacheActions:
cache_key: str,
request: PreparedRequest,
cache_control: bool = False,
- **kwargs,
- ):
- """Initialize from request info and cache settings"""
- if cache_control and has_cache_headers(request.headers):
- return cls.from_headers(cache_key, request.headers)
- else:
- return cls.from_settings(cache_key, request.url, cache_control=cache_control, **kwargs)
-
- @classmethod
- def from_headers(cls, cache_key: str, headers: Mapping):
- """Initialize from request headers"""
- directives = get_cache_directives(headers)
- do_not_cache = directives.get('max-age') == DO_NOT_CACHE
- return cls(
- cache_control=True,
- cache_key=cache_key,
- expire_after=directives.get('max-age'),
- skip_read=do_not_cache or 'no-store' in directives or 'no-cache' in directives,
- skip_write=do_not_cache or 'no-store' in directives,
- )
-
- @classmethod
- def from_settings(
- cls,
- cache_key: str,
- url: str = None,
- cache_control: bool = True,
- request_expire_after: ExpirationTime = None,
session_expire_after: ExpirationTime = None,
urls_expire_after: ExpirationPatterns = None,
**kwargs,
):
- """Initialize from cache settings"""
+ """Initialize from request info and cache settings"""
# Check expire_after values in order of precedence
+ directives = get_cache_directives(request.headers)
expire_after = coalesce(
- request_expire_after,
- get_url_expiration(url, urls_expire_after),
+ directives.get('max-age'),
+ get_url_expiration(request.url, urls_expire_after),
session_expire_after,
)
-
do_not_cache = expire_after == DO_NOT_CACHE
+
return cls(
cache_control=cache_control,
cache_key=cache_key,
expire_after=expire_after,
- skip_read=do_not_cache,
- skip_write=do_not_cache,
+ skip_read=do_not_cache or 'no-store' in directives or 'no-cache' in directives,
+ skip_write=do_not_cache or 'no-store' in directives,
)
@property
@@ -122,8 +95,11 @@ class CacheActions:
return get_expiration_datetime(self.expire_after)
def update_from_cached_response(self, response: CachedResponse):
- """Used after fetching a cached response, but before potentially sending a new request.
- Check for relevant cache headers on a cached response, and set corresponding request headers.
+ """Check for relevant cache headers on a cached response, and set corresponding request
+ headers for a conditional request, if possible.
+
+ Used after fetching a cached response, but before potentially sending a new request
+ (if expired).
"""
if not response or not response.is_expired:
return
@@ -135,15 +111,18 @@ class CacheActions:
self.request_headers = {k: v for k, v in self.request_headers.items() if v}
def update_from_response(self, response: Response):
- """Used after receiving a new response but before saving it to the cache.
- Update expiration + actions based on response headers, if not previously set.
+ """Update expiration + actions based on response headers, if not previously set.
+
+ Used after receiving a new response but before saving it to the cache.
"""
if not self.cache_control or not response:
return
directives = get_cache_directives(response.headers)
do_not_cache = directives.get('max-age') == DO_NOT_CACHE
- self.expire_after = coalesce(self.expires, directives.get('max-age'), directives.get('expires'))
+ self.expire_after = coalesce(
+ directives.get('max-age'), directives.get('expires'), self.expire_after
+ )
self.skip_write = self.skip_write or do_not_cache or 'no-store' in directives
@@ -173,6 +152,12 @@ def get_expiration_datetime(expire_after: ExpirationTime) -> Optional[datetime]:
return datetime.utcnow() + expire_after
+def get_expiration_seconds(expire_after: ExpirationTime) -> Optional[float]:
+ """Convert an expiration value in any supported format to an expiration time in seconds"""
+ expires = get_expiration_datetime(expire_after)
+ return (expires - datetime.utcnow()).total_seconds() if expires else None
+
+
def get_cache_directives(headers: Mapping) -> Dict:
"""Get all Cache-Control directives, and handle multiple headers and comma-separated lists"""
if not headers:
@@ -200,12 +185,6 @@ def get_url_expiration(
return None
-def has_cache_headers(headers: Mapping) -> bool:
- """Determine if headers contain supported cache directives"""
- has_cache_control = any([d in headers.get('Cache-Control', '') for d in CACHE_DIRECTIVES])
- return has_cache_control or bool(headers.get('Expires'))
-
-
def parse_http_date(value: str) -> Optional[datetime]:
"""Attempt to parse an HTTP (RFC 5322-compatible) timestamp"""
try: