diff options
author | Simon Hewitt <simon@archera.ai> | 2023-04-18 18:42:59 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2023-04-25 13:15:23 -0400 |
commit | e3187d80896078fdd03377a374978f42e8ad28ec (patch) | |
tree | 569171014b9e161fc4cc36b736ad9cf372a4a5e1 | |
parent | 0fe5b17957a4417fa39ccd038bd861c097d5b896 (diff) | |
download | dogpile-cache-e3187d80896078fdd03377a374978f42e8ad28ec.tar.gz |
handle CantDeserializeException raised from deserialize method
Added new construct :class:`.api.CantDeserializeException` which can be
raised by user-defined deserializer functions which would be passed to
:paramref:`.CacheRegion.deserializer`, to indicate a cache value that can't
be deserialized and therefore should be regenerated. This can allow an
application that's been updated to gracefully re-cache old items that were
persisted from a previous version of the application. Pull request courtesy
Simon Hewitt.
Closes: #236
Pull-request: https://github.com/sqlalchemy/dogpile.cache/pull/236
Pull-request-sha: f2ec26521acb8069d092c51749952f8540b5d75c
Change-Id: Idec175b9c06274628d3d027024f9878abb1d188b
-rw-r--r-- | docs/build/changelog.rst | 2 | ||||
-rw-r--r-- | docs/build/unreleased/236.rst | 11 | ||||
-rw-r--r-- | dogpile/__init__.py | 2 | ||||
-rw-r--r-- | dogpile/cache/api.py | 9 | ||||
-rw-r--r-- | dogpile/cache/region.py | 24 | ||||
-rw-r--r-- | tests/cache/_fixtures.py | 18 |
6 files changed, 60 insertions, 6 deletions
diff --git a/docs/build/changelog.rst b/docs/build/changelog.rst index a4eedf3..c210ba3 100644 --- a/docs/build/changelog.rst +++ b/docs/build/changelog.rst @@ -3,7 +3,7 @@ Changelog ========= .. changelog:: - :version: 1.1.9 + :version: 1.2.0 :include_notes_from: unreleased .. changelog:: diff --git a/docs/build/unreleased/236.rst b/docs/build/unreleased/236.rst new file mode 100644 index 0000000..ebd06bd --- /dev/null +++ b/docs/build/unreleased/236.rst @@ -0,0 +1,11 @@ +.. change:: + :tags: feature, region + :tickets: 236 + + Added new construct :class:`.api.CantDeserializeException` which can be + raised by user-defined deserializer functions which would be passed to + :paramref:`.CacheRegion.deserializer`, to indicate a cache value that can't + be deserialized and therefore should be regenerated. This can allow an + application that's been updated to gracefully re-cache old items that were + persisted from a previous version of the application. Pull request courtesy + Simon Hewitt. diff --git a/dogpile/__init__.py b/dogpile/__init__.py index 175c208..52b2f14 100644 --- a/dogpile/__init__.py +++ b/dogpile/__init__.py @@ -1,4 +1,4 @@ -__version__ = "1.1.9" +__version__ = "1.2.0" from .lock import Lock # noqa from .lock import NeedRegenerationException # noqa diff --git a/dogpile/cache/api.py b/dogpile/cache/api.py index 0717d43..c773463 100644 --- a/dogpile/cache/api.py +++ b/dogpile/cache/api.py @@ -51,6 +51,15 @@ Serializer = Callable[[ValuePayload], bytes] Deserializer = Callable[[bytes], ValuePayload] +class CantDeserializeException(Exception): + """Exception indicating deserialization failed, and that caching + should proceed to re-generate a value + + .. versionadded:: 1.2.0 + + """ + + class CacheMutex(abc.ABC): """Describes a mutexing object with acquire and release methods. diff --git a/dogpile/cache/region.py b/dogpile/cache/region.py index ef0dbc4..79f7eaa 100644 --- a/dogpile/cache/region.py +++ b/dogpile/cache/region.py @@ -27,6 +27,7 @@ from .api import BackendFormatted from .api import CachedValue from .api import CacheMutex from .api import CacheReturnType +from .api import CantDeserializeException from .api import KeyType from .api import MetaDataType from .api import NO_VALUE @@ -328,7 +329,17 @@ class CacheRegion: deserializer recommended by the backend will be used. Typical deserializers include ``pickle.dumps`` and ``json.dumps``. - .. versionadded:: 1.1.0 + Deserializers can raise a :class:`.api.CantDeserializeException` if they + are unable to deserialize the value from the backend, indicating + deserialization failed and that caching should proceed to re-generate + a value. This allows an application that has been updated to gracefully + re-cache old items which were persisted by a previous version of the + application and can no longer be successfully deserialized. + + .. versionadded:: 1.1.0 added "deserializer" parameter + + .. versionadded:: 1.2.0 added support for + :class:`.api.CantDeserializeException` :param async_creation_runner: A callable that, when specified, will be passed to and called by dogpile.lock when @@ -1219,8 +1230,12 @@ class CacheRegion: bytes_metadata, _, bytes_payload = byte_value.partition(b"|") metadata = json.loads(bytes_metadata) - payload = self.deserializer(bytes_payload) - return CachedValue(payload, metadata) + try: + payload = self.deserializer(bytes_payload) + except CantDeserializeException: + return NO_VALUE + else: + return CachedValue(payload, metadata) def _serialize_cached_value_elements( self, payload: ValuePayload, metadata: MetaDataType @@ -1247,7 +1262,8 @@ class CacheRegion: return self._serialize_cached_value_elements(payload, metadata) def _serialized_cached_value(self, value: CachedValue) -> BackendFormatted: - """Return a backend formatted representation of a :class:`.CachedValue`. + """Return a backend formatted representation of a + :class:`.CachedValue`. If a serializer is in use then this will return a string representation with the value formatted by the serializer. diff --git a/tests/cache/_fixtures.py b/tests/cache/_fixtures.py index 9e71f8f..8b3aa77 100644 --- a/tests/cache/_fixtures.py +++ b/tests/cache/_fixtures.py @@ -14,6 +14,7 @@ from dogpile.cache import CacheRegion from dogpile.cache import register_backend from dogpile.cache.api import CacheBackend from dogpile.cache.api import CacheMutex +from dogpile.cache.api import CantDeserializeException from dogpile.cache.api import NO_VALUE from dogpile.cache.region import _backend_loader from . import assert_raises_message @@ -380,6 +381,10 @@ class _GenericBackendTest(_GenericBackendFixture, TestCase): ) +def raise_cant_deserialize_exception(v): + raise CantDeserializeException() + + class _GenericSerializerTest(TestCase): # Inheriting from this class will make test cases # use these serialization arguments @@ -388,6 +393,19 @@ class _GenericSerializerTest(TestCase): "deserializer": json.loads, } + def test_serializer_cant_deserialize(self): + region = self._region( + region_args={ + "serializer": self.region_args["serializer"], + "deserializer": raise_cant_deserialize_exception, + } + ) + + value = {"foo": ["bar", 1, False, None]} + region.set("k", value) + asserted = region.get("k") + eq_(asserted, NO_VALUE) + def test_uses_serializer(self): region = self._region() |