diff options
-rw-r--r-- | requests_cache/_utils.py | 4 | ||||
-rw-r--r-- | requests_cache/cache_keys.py | 10 | ||||
-rw-r--r-- | requests_cache/policy/actions.py | 22 | ||||
-rw-r--r-- | tests/conftest.py | 8 | ||||
-rw-r--r-- | tests/unit/test_session.py | 16 |
5 files changed, 55 insertions, 5 deletions
diff --git a/requests_cache/_utils.py b/requests_cache/_utils.py index 591c4c1..ea6948f 100644 --- a/requests_cache/_utils.py +++ b/requests_cache/_utils.py @@ -23,11 +23,15 @@ def decode(value, encoding='utf-8') -> str: """Decode a value from bytes, if hasn't already been. Note: ``PreparedRequest.body`` is always encoded in utf-8. """ + if not value: + return '' return value.decode(encoding) if isinstance(value, bytes) else value def encode(value, encoding='utf-8') -> bytes: """Encode a value to bytes, if it hasn't already been""" + if not value: + return b'' return value if isinstance(value, bytes) else str(value).encode(encoding) diff --git a/requests_cache/cache_keys.py b/requests_cache/cache_keys.py index 1652bd7..c2f295a 100644 --- a/requests_cache/cache_keys.py +++ b/requests_cache/cache_keys.py @@ -28,7 +28,13 @@ from url_normalize import url_normalize from ._utils import decode, encode -__all__ = ['create_key', 'normalize_request'] +__all__ = [ + 'create_key', + 'normalize_body', + 'normalize_headers', + 'normalize_request', + 'normalize_url', +] if TYPE_CHECKING: from .models import AnyPreparedRequest, AnyRequest, CachedResponse @@ -117,7 +123,7 @@ def normalize_request( def normalize_headers( - headers: MutableMapping[str, str], ignored_parameters: ParamList + headers: MutableMapping[str, str], ignored_parameters: ParamList = None ) -> CaseInsensitiveDict: """Sort and filter request headers, and normalize minor variations in multi-value headers""" if ignored_parameters: diff --git a/requests_cache/policy/actions.py b/requests_cache/policy/actions.py index ad201f9..aedd4eb 100644 --- a/requests_cache/policy/actions.py +++ b/requests_cache/policy/actions.py @@ -1,11 +1,12 @@ from datetime import datetime, timedelta -from logging import getLogger -from typing import TYPE_CHECKING, Dict, Optional, Union +from logging import DEBUG, getLogger +from typing import TYPE_CHECKING, Dict, List, MutableMapping, Optional, Union from attr import define, field from requests import PreparedRequest, Response from .._utils import coalesce +from ..cache_keys import normalize_headers from ..models import RichMixin from . import ( DO_NOT_CACHE, @@ -291,7 +292,22 @@ class CacheActions(RichMixin): # Generate a secondary cache key based on Vary for both the cached request and new request key_kwargs['match_headers'] = [k.strip() for k in vary.split(',')] vary_cache_key = create_key(cached_response.request, **key_kwargs) - return create_key(self._request, **key_kwargs) == vary_cache_key + headers_match = create_key(self._request, **key_kwargs) == vary_cache_key + if not headers_match: + _log_vary_diff( + self._request.headers, cached_response.request.headers, key_kwargs['match_headers'] + ) + return headers_match + + +def _log_vary_diff( + headers_1: MutableMapping[str, str], headers_2: MutableMapping[str, str], vary: List[str] +): + """Log which specific headers specified by Vary did not match, debug purposes""" + headers_1 = normalize_headers(headers_1) + headers_2 = normalize_headers(headers_2) + nonmatching = [k for k in vary if headers_1.get(k) != headers_2.get(k)] + logger.debug(f'Failed Vary check. Non-matching headers: {", ".join(nonmatching)}') def _log_cache_criteria(operation: str, criteria: Dict): diff --git a/tests/conftest.py b/tests/conftest.py index edf9f28..e09d7f1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -72,6 +72,7 @@ MOCKED_URL_HTTPS = 'https+mock://requests-cache.com/text' MOCKED_URL_JSON = 'http+mock://requests-cache.com/json' MOCKED_URL_REDIRECT = 'http+mock://requests-cache.com/redirect' MOCKED_URL_REDIRECT_TARGET = 'http+mock://requests-cache.com/redirect_target' +MOCKED_URL_VARY = 'http+mock://requests-cache.com/vary' MOCKED_URL_404 = 'http+mock://requests-cache.com/nonexistent' MOCKED_URL_500 = 'http+mock://requests-cache.com/answer?q=this-statement-is-false' MOCK_PROTOCOLS = ['mock://', 'http+mock://', 'https+mock://'] @@ -206,6 +207,13 @@ def get_mock_adapter() -> Adapter: text='mock redirected response', status_code=200, ) + adapter.register_uri( + ANY_METHOD, + MOCKED_URL_VARY, + headers={'Content-Type': 'text/plain', 'Vary': 'Accept'}, + text='mock response with Vary header', + status_code=200, + ) adapter.register_uri(ANY_METHOD, MOCKED_URL_404, status_code=404) adapter.register_uri(ANY_METHOD, MOCKED_URL_500, status_code=500) return adapter diff --git a/tests/unit/test_session.py b/tests/unit/test_session.py index 9a2e0dc..b2ed786 100644 --- a/tests/unit/test_session.py +++ b/tests/unit/test_session.py @@ -28,6 +28,7 @@ from tests.conftest import ( MOCKED_URL_JSON, MOCKED_URL_REDIRECT, MOCKED_URL_REDIRECT_TARGET, + MOCKED_URL_VARY, patch_normalize_url, ) @@ -306,6 +307,21 @@ def test_match_headers__list(mock_session): assert mock_session.get(MOCKED_URL, headers=headers_3).from_cache is False +def test_match_headers__vary(mock_session): + """Vary should be used to validate headers, if available. + It should also override `match_headers` for the secondary cache key, if both are provided. + """ + # mock_session.settings.match_headers = ['Accept-Encoding'] + headers_1 = {'Accept': 'application/json', 'User-Agent': 'qutebrowser'} + headers_2 = {'Accept': 'application/json', 'User-Agent': 'Firefox'} + headers_3 = {'Accept': 'text/plain', 'User-Agent': 'qutebrowser'} + + assert mock_session.get(MOCKED_URL_VARY, headers=headers_1).from_cache is False + assert mock_session.get(MOCKED_URL_VARY, headers=headers_1).from_cache is True + assert mock_session.get(MOCKED_URL_VARY, headers=headers_2).from_cache is True + assert mock_session.get(MOCKED_URL_VARY, headers=headers_3).from_cache is False + + def test_include_get_headers(): """include_get_headers is aliased to match_headers for backwards-compatibility""" session = CachedSession(include_get_headers=True, backend='memory') |