diff options
author | Jordan Cook <jordan.cook.git@proton.me> | 2022-10-26 14:42:24 -0500 |
---|---|---|
committer | Jordan Cook <jordan.cook.git@proton.me> | 2022-10-28 12:46:53 -0500 |
commit | e9e6676913d466532e9220c38f754543e8f5d0e9 (patch) | |
tree | 57556541350187cd62f7b4dffe361627ab2c363b | |
parent | b4e270f6b129b75003d754987acea9e2d8670a5e (diff) | |
download | requests-cache-e9e6676913d466532e9220c38f754543e8f5d0e9.tar.gz |
Handle errors due to invalid responses in deserialize(), so it applies to other methods besides just get_responses()
-rw-r--r-- | requests_cache/backends/base.py | 24 | ||||
-rw-r--r-- | tests/integration/base_cache_test.py | 4 | ||||
-rw-r--r-- | tests/unit/test_base_cache.py | 28 | ||||
-rw-r--r-- | tests/unit/test_session.py | 5 |
4 files changed, 41 insertions, 20 deletions
diff --git a/requests_cache/backends/base.py b/requests_cache/backends/base.py index bc57ca1..825cd55 100644 --- a/requests_cache/backends/base.py +++ b/requests_cache/backends/base.py @@ -70,11 +70,7 @@ class BaseCache: response = self.responses[self.redirects[key]] response.cache_key = key return response - except KeyError: - return default - except DESERIALIZE_ERRORS as e: - logger.error(f'Unable to deserialize response {key}: {str(e)}') - logger.debug(e, exc_info=True) + except (AttributeError, KeyError): return default def save_response(self, response: Response, cache_key: str = None, expires: datetime = None): @@ -394,16 +390,28 @@ class BaseStorage(MutableMapping[KT, VT], ABC): """Close any open backend connections""" def serialize(self, value: VT): - """Serialize value, if a serializer is available""" + """Serialize a value, if a serializer is available""" if TYPE_CHECKING: assert hasattr(self.serializer, 'dumps') return self.serializer.dumps(value) if self.serializer else value def deserialize(self, value: VT): - """Deserialize value, if a serializer is available""" + """Deserialize a value, if a serializer is available. + + If deserialization fails (usually due to a value saved in an older requests-cache version), + ``None`` will be returned. + """ + if not self.serializer: + return value if TYPE_CHECKING: assert hasattr(self.serializer, 'loads') - return self.serializer.loads(value) if self.serializer else value + + try: + return self.serializer.loads(value) + except DESERIALIZE_ERRORS as e: + logger.error(f'Unable to deserialize response: {str(e)}') + logger.debug(e, exc_info=True) + return None def __str__(self): return str(list(self.keys())) diff --git a/tests/integration/base_cache_test.py b/tests/integration/base_cache_test.py index 4a55504..a7d2412 100644 --- a/tests/integration/base_cache_test.py +++ b/tests/integration/base_cache_test.py @@ -16,7 +16,7 @@ import requests from requests import PreparedRequest, Session from requests_cache import ALL_METHODS, CachedResponse, CachedSession -from requests_cache.backends.base import BaseCache +from requests_cache.backends import BaseCache from tests.conftest import ( CACHE_NAME, ETAG, @@ -99,7 +99,7 @@ class BaseCacheTest: # Patch storage class to track number of times getitem is called, without changing behavior with patch.object( - storage_class, '__getitem__', side_effect=storage_class.__getitem__ + storage_class, '__getitem__', side_effect=lambda k: CachedResponse() ) as getitem: session.get(httpbin('get')) assert getitem.call_count == 1 diff --git a/tests/unit/test_base_cache.py b/tests/unit/test_base_cache.py index e0b6b0e..23b4e4f 100644 --- a/tests/unit/test_base_cache.py +++ b/tests/unit/test_base_cache.py @@ -1,4 +1,5 @@ """BaseCache tests that use mocked responses only""" +import pickle from datetime import datetime, timedelta from logging import getLogger from pickle import PickleError @@ -11,6 +12,7 @@ from requests import Request from requests_cache.backends import BaseCache, SQLiteDict from requests_cache.cache_keys import create_key from requests_cache.models import CachedRequest, CachedResponse +from requests_cache.session import CachedSession from tests.conftest import ( MOCKED_URL, MOCKED_URL_ETAG, @@ -18,6 +20,7 @@ from tests.conftest import ( MOCKED_URL_JSON, MOCKED_URL_REDIRECT, ignore_deprecation, + mount_mock_adapter, patch_normalize_url, ) @@ -109,19 +112,28 @@ def test_delete__expired__per_request(mock_session): assert len(mock_session.cache.responses) == 1 -def test_delete__invalid(mock_session): +def test_delete__invalid(tempfile_path): + class BadSerialzier: + def dumps(self, value): + return pickle.dumps(value) + + def loads(self, value): + response = pickle.loads(value) + if response.url.endswith('/json'): + raise PickleError + return response + + mock_session = CachedSession( + cache_name=tempfile_path, backend='sqlite', serializer=BadSerialzier() + ) + mock_session = mount_mock_adapter(mock_session) + # Start with two cached responses, one of which will raise an error response_1 = mock_session.get(MOCKED_URL) response_2 = mock_session.get(MOCKED_URL_JSON) - def error_on_key(key): - if key == response_2.cache_key: - raise PickleError - return CachedResponse.from_response(response_1) - # Use the generic BaseCache implementation, not the SQLite-specific one - with patch.object(SQLiteDict, '__getitem__', side_effect=error_on_key): - BaseCache.delete(mock_session.cache, expired=True, invalid=True) + BaseCache.delete(mock_session.cache, expired=True, invalid=True) assert len(mock_session.cache.responses) == 1 assert mock_session.get(MOCKED_URL).from_cache is True diff --git a/tests/unit/test_session.py b/tests/unit/test_session.py index 60e8bb4..116b74f 100644 --- a/tests/unit/test_session.py +++ b/tests/unit/test_session.py @@ -351,7 +351,8 @@ def test_include_get_headers(): def test_cache_error(exception_cls, mock_session): """If there is an error while fetching a cached response, a new one should be fetched""" mock_session.get(MOCKED_URL) - with patch.object(SQLiteDict, '__getitem__', side_effect=exception_cls): + + with patch.object(mock_session.cache.responses.serializer, 'loads', side_effect=exception_cls): assert mock_session.get(MOCKED_URL).from_cache is False @@ -440,7 +441,7 @@ def test_unpickle_errors(mock_session): """If there is an error during deserialization, the request should be made again""" assert mock_session.get(MOCKED_URL_JSON).from_cache is False - with patch.object(SQLiteDict, '__getitem__', side_effect=PickleError): + with patch.object(mock_session.cache.responses.serializer, 'loads', side_effect=PickleError): resp = mock_session.get(MOCKED_URL_JSON) assert resp.from_cache is False assert resp.json()['message'] == 'mock json response' |