summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--requests_cache/_utils.py4
-rw-r--r--requests_cache/cache_keys.py10
-rw-r--r--requests_cache/policy/actions.py22
-rw-r--r--tests/conftest.py8
-rw-r--r--tests/unit/test_session.py16
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')