Refactor request-level settings into separate RequestSettings class
diff --git a/requests_cache/cache_control.py b/requests_cache/cache_control.py
index 9f155f8..10a1de8 100644
--- a/requests_cache/cache_control.py
+++ b/requests_cache/cache_control.py
@@ -27,7 +27,7 @@
__all__ = ['DO_NOT_CACHE', 'CacheActions']
if TYPE_CHECKING:
- from .models import CachedResponse, CacheSettings
+ from .models import CachedResponse, CacheSettings, RequestSettings
# May be set by either headers or expire_after param to disable caching or disable expiration
DO_NOT_CACHE = 0
@@ -42,32 +42,19 @@
@define
class CacheActions:
- """A class that translates cache settings and headers into specific actions to take for a
- given cache item. Actions include:
+ """Translates cache settings and headers into specific actions to take for a given cache item.
- * Read from the cache
- * Write to the cache
- * Revalidate cache item (if it exists)
- * Set cache expiration
- * Add headers for conditional requests
-
- If multiple sources provide an expiration time, they will be used in the following order of
- precedence:
-
- 1. Cache-Control request headers
- 2. Cache-Control response headers (if enabled)
- 3. Per-request expiration
- 4. Per-URL expiration
- 5. Per-session expiration
-
- See :ref:`headers` for more details about behavior.
+ * See :ref:`precedence` for behavior if multiple sources provide an expiration
+ * See :ref:`headers` for more details about header behavior
"""
cache_key: str = field(default=None)
expire_after: ExpirationTime = field(default=None)
- only_if_cached: bool = field(default=False)
request_directives: Dict[str, CacheDirective] = field(factory=dict)
+ resend_request: bool = field(default=False)
revalidate: bool = field(default=False)
+ error_504: bool = field(default=False)
+ send_request: bool = field(default=False)
settings: CacheSettings = field(default=None)
skip_read: bool = field(default=False)
skip_write: bool = field(default=False)
@@ -77,12 +64,8 @@
def from_request(
cls,
cache_key: str,
- settings: 'CacheSettings',
request: PreparedRequest,
- request_expire_after: ExpirationTime = None,
- only_if_cached: bool = False,
- refresh: bool = False,
- revalidate: bool = False,
+ settings: 'RequestSettings',
**kwargs,
):
"""Initialize from request info and cache settings.
@@ -102,7 +85,7 @@
# Check expiration values in order of precedence
expire_after = coalesce(
directives.get('max-age'),
- request_expire_after,
+ settings.request_expire_after,
get_url_expiration(request.url, settings.urls_expire_after),
settings.expire_after,
)
@@ -113,14 +96,15 @@
skip_write = check_expiration == DO_NOT_CACHE or 'no-store' in directives
# These behaviors may be set by either request headers or keyword arguments
- only_if_cached = only_if_cached or 'only-if-cached' in directives
- revalidate = revalidate or 'no-cache' in directives
- skip_read = skip_write or refresh or bool(refresh_temp_header)
+ settings.only_if_cached = settings.only_if_cached or 'only-if-cached' in directives
+ revalidate = settings.revalidate or 'no-cache' in directives
+ skip_read = any(
+ [settings.refresh, skip_write, bool(refresh_temp_header), settings.disabled]
+ )
return cls(
cache_key=cache_key,
expire_after=expire_after,
- only_if_cached=only_if_cached,
request_directives=directives,
revalidate=revalidate,
skip_read=skip_read,
@@ -139,7 +123,16 @@
Used after fetching a cached response, but before potentially sending a new request.
"""
- if not response:
+ # Determine if we need to send a new request or respond with an error
+ is_expired = getattr(response, 'is_expired', False)
+ if self.settings.only_if_cached and (response is None or is_expired):
+ self.error_504 = True
+ elif response is None:
+ self.send_request = True
+ elif is_expired and not (self.settings.only_if_cached and self.settings.stale_if_error):
+ self.resend_request = True
+
+ if response is None:
return
# Revalidation may be triggered by either stale response or request/cached response headers
@@ -154,16 +147,13 @@
)
if self.revalidate:
+ self.send_request = True
if response.headers.get('ETag'):
self.validation_headers['If-None-Match'] = response.headers['ETag']
if response.headers.get('Last-Modified'):
self.validation_headers['If-Modified-Since'] = response.headers['Last-Modified']
- def update_from_response(
- self,
- response: Response,
- cache_disabled: bool = False,
- ):
+ def update_from_response(self, response: Response):
"""Update expiration + actions based on headers from a new response.
Used after receiving a new response but before saving it to the cache.
@@ -191,9 +181,9 @@
# Apply filter callback, if any
filtered_out = self.settings.filter_fn is not None and not self.settings.filter_fn(response)
- # Perform remaining checks needed to determine if the given response should be cached
+ # Apply and log remaining checks needed to determine if the response should be cached
cache_criteria = {
- 'disabled cache': cache_disabled,
+ 'disabled cache': self.settings.disabled,
'disabled method': str(response.request.method) not in self.settings.allowable_methods,
'disabled status': response.status_code not in self.settings.allowable_codes,
'disabled by filter': filtered_out,
@@ -202,6 +192,12 @@
logger.debug(f'Pre-cache checks for response from {response.url}: {cache_criteria}')
self.skip_write = any(cache_criteria.values())
+ def update_request(self, request: PreparedRequest) -> PreparedRequest:
+ """Apply validation headers (if any) before sending a request"""
+ # if self.revalidate:
+ request.headers.update(self.validation_headers)
+ return request
+
def update_revalidated_response(
self, response: Response, cached_response: CachedResponse
) -> CachedResponse:
diff --git a/requests_cache/models/__init__.py b/requests_cache/models/__init__.py
index 497ca3f..1931ea2 100644
--- a/requests_cache/models/__init__.py
+++ b/requests_cache/models/__init__.py
@@ -7,7 +7,7 @@
from .raw_response import CachedHTTPResponse
from .request import CachedRequest
from .response import CachedResponse, set_response_defaults
-from .settings import CacheSettings
+from .settings import CacheSettings, RequestSettings
AnyResponse = Union[Response, CachedResponse]
AnyRequest = Union[Request, PreparedRequest, CachedRequest]
diff --git a/requests_cache/models/settings.py b/requests_cache/models/settings.py
index 01ad937..b32df3d 100644
--- a/requests_cache/models/settings.py
+++ b/requests_cache/models/settings.py
@@ -1,6 +1,6 @@
from typing import TYPE_CHECKING, Callable, Dict, Iterable, Union
-from attr import define, field
+from attr import asdict, define, field
from .._utils import get_valid_kwargs
from ..cache_control import ExpirationTime
@@ -13,14 +13,24 @@
KeyCallback = Callable[..., str]
+# TODO: RequestSettings subclass?
+# This would work by merging settings for an individual request on top of session settings
+# This would also allow for *any* session setting to be passed as a per-request setting
+# ...But only for send(), not request()
+# TODO: HeaderSettings class?
+# This would encapsulate settings from Cache-Control directives and other cache headers
+# Downside: that logic would then live in this moduel instead of in cache_control (which may be a better fit)
+
+
@define(init=False)
class CacheSettings:
- """Class to store cache settings used by :py:class:`.CachedSession` and backends.
+ """Settings that affect caching behavior, used by :py:class:`.CachedSession` and
+ :py:class:`.BaseCache`.
Args:
allowable_codes: Only cache responses with one of these status codes
allowable_methods: Cache only responses for one of these HTTP methods
- cache_control: Use Cache-Control headers to set expiration
+ cache_control: Use Cache-Control and other response headers to set expiration
expire_after: Time after which cached items will expire
filter_fn: Response filtering function that indicates whether or not a given response should
be cached.
@@ -35,12 +45,13 @@
allowable_codes: Iterable[int] = field(default=(200,))
allowable_methods: Iterable[str] = field(default=('GET', 'HEAD'))
cache_control: bool = field(default=False)
- # cache_disabled: bool = field(default=False)
+ disabled: bool = field(default=False)
expire_after: ExpirationTime = field(default=-1)
filter_fn: FilterCallback = field(default=None)
ignored_parameters: Iterable[str] = field(default=None)
key_fn: KeyCallback = field(default=None)
match_headers: Union[Iterable[str], bool] = field(default=False)
+ only_if_cached: bool = field(default=False)
stale_if_error: bool = field(default=False)
urls_expire_after: Dict[str, ExpirationTime] = field(factory=dict)
@@ -54,3 +65,18 @@
# Ignore invalid kwargs for easier initialization from mixed **kwargs
kwargs = get_valid_kwargs(self.__attrs_init__, kwargs)
self.__attrs_init__(**kwargs)
+
+
+@define(init=False)
+class RequestSettings(CacheSettings):
+ """Cache settings that may be set for an individual request"""
+
+ refresh: bool = field(default=False)
+ revalidate: bool = field(default=False)
+ request_expire_after: ExpirationTime = field(default=-1)
+
+ def __init__(self, session_settings: CacheSettings, **kwargs):
+ # Start with session-level cache settings and add/override with request-level settings
+ kwargs['request_expire_after'] = kwargs.pop('expire_after', None)
+ kwargs = {**asdict(session_settings), **kwargs}
+ super().__init__(**kwargs)
diff --git a/requests_cache/session.py b/requests_cache/session.py
index b766330..218605d 100644
--- a/requests_cache/session.py
+++ b/requests_cache/session.py
@@ -32,7 +32,13 @@
get_504_response,
get_expiration_seconds,
)
-from .models import AnyResponse, CachedResponse, CacheSettings, set_response_defaults
+from .models import (
+ AnyResponse,
+ CachedResponse,
+ CacheSettings,
+ RequestSettings,
+ set_response_defaults,
+)
__all__ = ['ALL_METHODS', 'CachedSession', 'CacheMixin']
ALL_METHODS = ['GET', 'HEAD', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE']
@@ -45,6 +51,10 @@
MIXIN_BASE = object
+# TODO: Better docs for __init__
+# TODO: Better function signatures (due to passing around **kwargs instead of explicit keyword args)
+
+
class CacheMixin(MIXIN_BASE):
"""Mixin class that extends :py:class:`requests.Session` with caching features.
See :py:class:`.CachedSession` for usage details.
@@ -54,17 +64,13 @@
self,
cache_name: str = 'http_cache',
backend: BackendSpecifier = None,
- settings: CacheSettings = None,
**kwargs,
):
- settings = settings or CacheSettings(**kwargs)
- self.cache = init_backend(cache_name, backend, settings=settings, **kwargs)
- self._disabled = False
+ self.cache = init_backend(cache_name, backend, **kwargs)
self._lock = RLock()
- # If the superclass is custom Session, pass along any valid kwargs
- session_kwargs = get_valid_kwargs(super().__init__, kwargs)
- super().__init__(**session_kwargs) # type: ignore
+ # If the mixin superclass is custom Session, pass along any valid kwargs
+ super().__init__(**get_valid_kwargs(super().__init__, kwargs)) # type: ignore
def request( # type: ignore
self,
@@ -122,65 +128,43 @@
with patch_form_boundary(**kwargs):
return super().request(method, url, *args, **kwargs)
- def send(
- self,
- request: PreparedRequest,
- expire_after: ExpirationTime = None,
- only_if_cached: bool = False,
- refresh: bool = False,
- revalidate: bool = False,
- **kwargs,
- ) -> AnyResponse:
+ def send(self, request: PreparedRequest, **kwargs) -> AnyResponse:
"""Send a prepared request, with caching. See :py:meth:`.request` for notes on behavior, and
- see :py:meth:`requests.Session.send` for parameters. Additional parameters:
-
- Args:
- expire_after: Expiration time to set only for this request
- only_if_cached: Only return results from the cache. If not cached, return a 504 response
- instead of sending a new request.
- refresh: Always make a new request, and overwrite any previously cached response
- revalidate: Revalidate with the server before using a cached response (e.g., a "soft refresh")
+ see :py:meth:`requests.Session.send` for parameters.
"""
- # Determine which actions to take based on request info and cache settings
- cache_key = self.cache.create_key(request, **kwargs)
+ # Determine which actions to take based on settings and request info
actions = CacheActions.from_request(
- cache_key=cache_key,
- request=request,
- request_expire_after=expire_after,
- only_if_cached=only_if_cached,
- refresh=refresh,
- revalidate=revalidate,
- settings=self.cache.settings,
+ self.cache.create_key(request, **kwargs),
+ request,
+ RequestSettings(self.settings, **kwargs),
**kwargs,
)
# Attempt to fetch a cached response
cached_response: Optional[CachedResponse] = None
- if not (self._disabled or actions.skip_read):
- cached_response = self.cache.get_response(cache_key)
+ if not actions.skip_read:
+ cached_response = self.cache.get_response(actions.cache_key)
actions.update_from_cached_response(cached_response)
- is_expired = getattr(cached_response, 'is_expired', False)
# Handle missing and expired responses based on settings and headers
- if (cached_response is None or is_expired) and actions.only_if_cached:
- response: AnyResponse = get_504_response(request)
- elif cached_response is None or actions.revalidate:
- response = self._send_and_cache(request, actions, cached_response, **kwargs)
- elif is_expired and self.cache.settings.stale_if_error and actions.only_if_cached:
- response = cached_response
- elif is_expired:
- response = self._resend(request, actions, cached_response, **kwargs)
+ if actions.send_request:
+ response: AnyResponse = self._send_and_cache(
+ request, actions, cached_response, **kwargs
+ )
+ elif actions.resend_request:
+ response = self._resend(request, actions, cached_response, **kwargs) # type: ignore
+ elif actions.error_504:
+ response = get_504_response(request)
else:
- response = cached_response
+ response = cached_response # type: ignore # Guaranteed to be non-None by this point
# If the request has been filtered out and was previously cached, delete it
- filter_fn = self.cache.settings.filter_fn
- if filter_fn is not None and not filter_fn(response):
+ if self.settings.filter_fn is not None and not self.settings.filter_fn(response):
logger.debug(f'Deleting filtered response for URL: {response.url}')
- self.cache.delete(cache_key)
+ self.cache.delete(actions.cache_key)
return response
- # Dispatch any hooks here, because they are removed before pickling
+ # Dispatch any hooks here, because they are removed during serialization
return dispatch_hook('response', request.hooks, response, **kwargs)
def _send_and_cache(
@@ -190,19 +174,16 @@
cached_response: CachedResponse = None,
**kwargs,
) -> AnyResponse:
- """Send the request and cache the response, unless disabled by settings or headers.
-
- If applicable, also add headers to make a conditional request. If we get a 304 Not Modified
- response, return the stale cache item.
+ """Send a request and cache the response, unless disabled by settings or headers.
+ If applicable, also handle conditional requests.
"""
- if actions.revalidate:
- request.headers.update(actions.validation_headers)
+ request = actions.update_request(request)
response = super().send(request, **kwargs)
actions.update_from_response(response)
if not actions.skip_write:
self.cache.save_response(response, actions.cache_key, actions.expires)
- elif cached_response and response.status_code == 304:
+ elif cached_response is not None and response.status_code == 304:
cached_response = actions.update_revalidated_response(response, cached_response)
self.cache.save_response(cached_response, actions.cache_key, actions.expires)
return cached_response
@@ -217,31 +198,31 @@
cached_response: CachedResponse,
**kwargs,
) -> AnyResponse:
- """Attempt to resend the request and cache the new response."""
+ """Handle a stale cached response by attempting to resend the request and cache a fresh
+ response
+ """
logger.debug('Stale response; attempting to re-send request')
try:
- # Attempt to send the request and cache the new response
response = self._send_and_cache(request, actions, cached_response, **kwargs)
- if self.cache.settings.stale_if_error:
+ if self.settings.stale_if_error:
response.raise_for_status()
return response
except Exception:
- return self._handle_error(cached_response)
+ return self._handle_error(cached_response, actions)
- def _handle_error(self, cached_response: CachedResponse) -> AnyResponse:
+ def _handle_error(self, cached_response: CachedResponse, actions: CacheActions) -> AnyResponse:
"""Handle a request error based on settings:
* Default behavior: delete the stale cache item and re-raise the error
* stale-if-error: Ignore the error and and return the stale cache item
"""
- if self.cache.settings.stale_if_error:
+ if self.settings.stale_if_error:
logger.warning(
f'Request for URL {cached_response.request.url} failed; using cached response',
exc_info=True,
)
return cached_response
else:
- # TODO: Ensure cache_key is always populated
- self.cache.delete(cached_response.cache_key or '')
+ self.cache.delete(actions.cache_key)
raise
@contextmanager
@@ -258,14 +239,22 @@
... s.get('http://httpbin.org/ip')
"""
- if self._disabled:
+ if self.settings.disabled:
yield
else:
- self._disabled = True
+ self.settings.disabled = True
try:
yield
finally:
- self._disabled = False
+ self.settings.disabled = False
+
+ @property
+ def settings(self) -> CacheSettings:
+ return self.cache.settings
+
+ @settings.setter
+ def settings(self, value: CacheSettings):
+ self.cache.settings = value
def remove_expired_responses(self, expire_after: ExpirationTime = None):
"""Remove expired responses from the cache, optionally with revalidation
@@ -276,25 +265,15 @@
self.cache.remove_expired_responses(expire_after)
def __repr__(self):
- repr_attrs = [
- 'cache',
- 'expire_after',
- 'urls_expire_after',
- 'allowable_codes',
- 'allowable_methods',
- 'stale_if_error',
- 'cache_control',
- ]
- attr_strs = [f'{k}={repr(getattr(self, k))}' for k in repr_attrs]
- return f'<CachedSession({", ".join(attr_strs)})>'
+ return f'<CachedSession(cache={self.cache}, settings={self.settings})>'
class CachedSession(CacheMixin, OriginalSession):
"""Session class that extends :py:class:`requests.Session` with caching features.
- See individual :py:mod:`backend classes <requests_cache.backends>` for additional backend-specific arguments.
- Also see :ref:`user-guide` for more details and examples on how the following arguments
- affect cache behavior.
+ See individual :py:mod:`backend classes <requests_cache.backends>` for additional
+ backend-specific arguments. Also see :ref:`user-guide` for more details and examples on how the
+ following arguments affect cache behavior.
Args:
cache_name: Cache prefix or namespace, depending on backend
@@ -302,6 +281,7 @@
``['sqlite', 'filesystem', 'mongodb', 'gridfs', 'redis', 'dynamodb', 'memory']``
serializer: Serializer name or instance; name may be one of
``['pickle', 'json', 'yaml', 'bson']``.
+ kwargs: Additional keyword arguments for :py:class:`.CacheSettings` or the selected backend
"""
diff --git a/tests/unit/test_cache_control.py b/tests/unit/test_cache_control.py
index 3b1f8a4..c942891 100644
--- a/tests/unit/test_cache_control.py
+++ b/tests/unit/test_cache_control.py
@@ -10,7 +10,7 @@
get_expiration_datetime,
get_url_expiration,
)
-from requests_cache.models.response import CachedResponse
+from requests_cache.models.response import CachedResponse, CacheSettings, RequestSettings
from tests.conftest import ETAG, HTTPDATE_DATETIME, HTTPDATE_STR, LAST_MODIFIED
IGNORED_DIRECTIVES = [
@@ -47,12 +47,12 @@
request.headers = {'Cache-Control': f'max-age={request_expire_after}'}
get_url_expiration.return_value = url_expire_after
+ settings = CacheSettings(cache_control=True, expire_after=1)
+ settings = RequestSettings(settings, expire_after=request_expire_after)
actions = CacheActions.from_request(
cache_key='key',
request=request,
- request_expire_after=request_expire_after,
- session_expire_after=1,
- cache_control=True,
+ settings=RequestSettings(settings, expire_after=request_expire_after),
)
assert actions.expire_after == expected_expiration
@@ -70,9 +70,8 @@
)
def test_init_from_headers(headers, expected_expiration):
"""Test with Cache-Control request headers"""
- actions = CacheActions.from_request(
- cache_key='key', cache_control=True, request=MagicMock(headers=headers)
- )
+ settings = RequestSettings(cache_control=True)
+ actions = CacheActions.from_request('key', MagicMock(headers=headers), settings)
assert actions.cache_key == 'key'
if expected_expiration == DO_NOT_CACHE:
@@ -104,22 +103,20 @@
)
def test_init_from_settings(url, request_expire_after, expected_expiration):
"""Test with per-session, per-request, and per-URL expiration"""
- urls_expire_after = {
- '*.site_1.com': timedelta(hours=12),
- 'site_2.com/resource_1': timedelta(hours=20),
- 'site_2.com/resource_2': timedelta(days=7),
- 'site_2.com/static': -1,
- }
+ settings = CacheSettings(
+ expire_after=1,
+ urls_expire_after={
+ '*.site_1.com': timedelta(hours=12),
+ 'site_2.com/resource_1': timedelta(hours=20),
+ 'site_2.com/resource_2': timedelta(days=7),
+ 'site_2.com/static': -1,
+ },
+ )
request = MagicMock(url=url)
if request_expire_after:
request.headers = {'Cache-Control': f'max-age={request_expire_after}'}
- actions = CacheActions.from_request(
- cache_key='key',
- request=request,
- session_expire_after=1,
- urls_expire_after=urls_expire_after,
- )
+ actions = CacheActions.from_request('key', request, RequestSettings(settings))
assert actions.expire_after == expected_expiration
@@ -148,12 +145,8 @@
headers=headers,
)
- actions = CacheActions.from_request(
- cache_key='key',
- cache_control=cache_control,
- request=request,
- session_expire_after=expire_after,
- )
+ settings = CacheSettings(cache_control=cache_control, expire_after=expire_after)
+ actions = CacheActions.from_request('key', request, RequestSettings(settings))
assert actions.expire_after == expected_expiration
assert actions.skip_read == expected_skip_read
@@ -173,8 +166,9 @@
def test_update_from_cached_response(response_headers, expected_validation_headers):
"""Conditional request headers should be added if the cached response is expired"""
actions = CacheActions.from_request(
- cache_key='key',
- request=MagicMock(url='https://img.site.com/base/img.jpg', headers={}),
+ 'key',
+ MagicMock(url='https://img.site.com/base/img.jpg', headers={}),
+ RequestSettings(),
)
cached_response = CachedResponse(
headers=response_headers, expires=datetime.now() - timedelta(1)
@@ -197,8 +191,9 @@
"""Conditional request headers should be added if requested by headers (even if the response
is not expired)"""
actions = CacheActions.from_request(
- cache_key='key',
- request=MagicMock(url='https://img.site.com/base/img.jpg', headers=request_headers),
+ 'key',
+ MagicMock(url='https://img.site.com/base/img.jpg', headers=request_headers),
+ RequestSettings(),
)
cached_response = CachedResponse(headers={'ETag': ETAG, **response_headers}, expires=None)
@@ -211,8 +206,9 @@
"""Conditional request headers should NOT be added if the cached response is not expired and
revalidation is not requested by headers"""
actions = CacheActions.from_request(
- cache_key='key',
- request=MagicMock(url='https://img.site.com/base/img.jpg', headers={}),
+ 'key',
+ MagicMock(url='https://img.site.com/base/img.jpg', headers={}),
+ RequestSettings(),
)
cached_response = CachedResponse(
headers={'ETag': ETAG, 'Last-Modified': LAST_MODIFIED}, expires=None
@@ -242,9 +238,7 @@
"""Test with Cache-Control response headers"""
url = 'https://img.site.com/base/img.jpg'
actions = CacheActions.from_request(
- cache_key='key',
- request=MagicMock(url=url),
- cache_control=True,
+ 'key', MagicMock(url=url), RequestSettings(cache_control=True)
)
actions.update_from_response(MagicMock(url=url, headers=headers))
@@ -259,7 +253,7 @@
def test_update_from_response__ignored():
url = 'https://img.site.com/base/img.jpg'
actions = CacheActions.from_request(
- cache_key='key', request=MagicMock(url=url), cache_control=False
+ 'key', MagicMock(url=url), RequestSettings(cache_control=False)
)
actions.update_from_response(MagicMock(url=url, headers={'Cache-Control': 'max-age=5'}))
assert actions.expire_after is None
@@ -275,7 +269,7 @@
url = 'https://img.site.com/base/img.jpg'
headers = {**cache_headers, **validator_headers}
actions = CacheActions.from_request(
- cache_key='key', request=MagicMock(url=url), cache_control=True
+ 'key', MagicMock(url=url), RequestSettings(cache_control=True)
)
actions.update_from_response(MagicMock(url=url, headers=headers))
assert actions.expires == mock_datetime.utcnow()
@@ -288,12 +282,9 @@
request = PreparedRequest()
request.url = 'https://img.site.com/base/img.jpg'
request.headers = {'Cache-Control': directive}
- actions = CacheActions.from_request(
- cache_key='key',
- request=request,
- session_expire_after=1,
- cache_control=True,
- )
+ settings = CacheSettings(expire_after=1, cache_control=True)
+ actions = CacheActions.from_request('key', request, RequestSettings(settings))
+
assert actions.expire_after == 1
assert actions.revalidate is False
assert actions.skip_read is False
diff --git a/tests/unit/test_session.py b/tests/unit/test_session.py
index b7246d1..86d10cb 100644
--- a/tests/unit/test_session.py
+++ b/tests/unit/test_session.py
@@ -52,8 +52,8 @@
)
assert session.cache.cache_name == 'test_cache'
- assert session.cache.ignored_parameters == ['foo']
- assert session.cache.match_headers is True
+ assert session.cache.settings.ignored_parameters == ['foo']
+ assert session.cache.settings.match_headers is True
def test_init_backend_class():
@@ -134,8 +134,8 @@
"""Test all relevant combinations of methods and data fields. Requests with different request
params, data, or json should not be cached under different keys based on an ignored param.
"""
- mock_session.cache.ignored_parameters = ['ignored']
- mock_session.cache.match_headers = True
+ mock_session.cache.settings.ignored_parameters = ['ignored']
+ mock_session.cache.settings.match_headers = True
params_1 = {'ignored': 'value_1', 'not_ignored': 'value_1'}
params_2 = {'ignored': 'value_2', 'not_ignored': 'value_1'}
params_3 = {'ignored': 'value_2', 'not_ignored': 'value_2'}
@@ -153,7 +153,7 @@
"""Test all relevant combinations of methods and data fields. Requests with ignored params
should have those values redacted from the cached response.
"""
- mock_session.cache.ignored_parameters = ['access_token']
+ mock_session.cache.settings.ignored_parameters = ['access_token']
params_1 = {'access_token': 'asdf', 'not_ignored': 'value_1'}
mock_session.request(method, MOCKED_URL, **{field: params_1})
@@ -296,7 +296,7 @@
def test_match_headers(mock_session):
"""With match_headers, requests with different headers should have different cache keys"""
- mock_session.cache.match_headers = True
+ mock_session.cache.settings.match_headers = True
headers_list = [
{'Accept': 'application/json'},
{'Accept': 'text/xml'},
@@ -310,7 +310,7 @@
def test_match_headers__normalize(mock_session):
"""With match_headers, the same headers (in any order) should have the same cache key"""
- mock_session.cache.match_headers = True
+ mock_session.cache.settings.match_headers = True
headers = {'Accept': 'application/json', 'Custom': 'abc'}
reversed_headers = {'Custom': 'abc', 'Accept': 'application/json'}
assert mock_session.get(MOCKED_URL, headers=headers).from_cache is False
@@ -319,7 +319,7 @@
def test_match_headers__list(mock_session):
"""match_headers can optionally be a list of specific headers to include"""
- mock_session.cache.match_headers = ['Accept']
+ mock_session.cache.settings.match_headers = ['Accept']
headers_1 = {'Accept': 'application/json', 'User-Agent': 'qutebrowser'}
headers_2 = {'Accept': 'application/json', 'User-Agent': 'Firefox'}
headers_3 = {'Accept': 'text/plain', 'User-Agent': 'qutebrowser'}
@@ -333,7 +333,7 @@
def test_include_get_headers():
"""include_get_headers is aliased to match_headers for backwards-compatibility"""
session = CachedSession(include_get_headers=True, backend='memory')
- assert session.cache.match_headers is True
+ assert session.cache.settings.match_headers is True
# Error handling
@@ -351,8 +351,8 @@
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 = 1
+ mock_session.cache.settings.stale_if_error = False
+ mock_session.cache.settings.expire_after = 1
mock_session.get(MOCKED_URL)
time.sleep(1)
@@ -364,8 +364,8 @@
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 = 1
+ mock_session.cache.settings.stale_if_error = True
+ mock_session.cache.settings.expire_after = 1
assert mock_session.get(MOCKED_URL).from_cache is False
assert mock_session.get(MOCKED_URL).from_cache is True
@@ -377,9 +377,9 @@
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 = 1
- mock_session.allowable_codes = (200, 404)
+ mock_session.cache.settings.stale_if_error = True
+ mock_session.cache.settings.expire_after = 1
+ mock_session.cache.settings.allowable_codes = (200, 404)
assert mock_session.get(MOCKED_URL_404).from_cache is False
@@ -391,7 +391,7 @@
def test_old_data_on_error():
"""stale_if_error is aliased to old_data_on_error for backwards-compatibility"""
session = CachedSession(old_data_on_error=True, backend='memory')
- assert session.stale_if_error is True
+ assert session.cache.settings.stale_if_error is True
def test_cache_disabled(mock_session):
@@ -431,7 +431,7 @@
def test_filter_fn(mock_session):
- mock_session.filter_fn = lambda r: r.request.url != MOCKED_URL_JSON
+ mock_session.cache.settings.filter_fn = lambda r: r.request.url != MOCKED_URL_JSON
mock_session.get(MOCKED_URL)
mock_session.get(MOCKED_URL_JSON)
@@ -442,7 +442,7 @@
def test_filter_fn__retroactive(mock_session):
"""filter_fn should also apply to previously cached responses"""
mock_session.get(MOCKED_URL_JSON)
- mock_session.filter_fn = lambda r: r.request.url != MOCKED_URL_JSON
+ mock_session.cache.settings.filter_fn = lambda r: r.request.url != MOCKED_URL_JSON
mock_session.get(MOCKED_URL_JSON)
assert not mock_session.cache.has_url(MOCKED_URL_JSON)
@@ -453,7 +453,7 @@
"""Create a key based on only the request URL (without params)"""
return request.url.split('?')[0]
- mock_session.cache.key_fn = create_key
+ mock_session.cache.settings.key_fn = create_key
mock_session.get(MOCKED_URL)
response = mock_session.get(MOCKED_URL, params={'k': 'v'})
assert response.from_cache is True