Skip to content

Commit e3187d8

Browse files
sjhewittzzzeek
authored andcommitted
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: #236 Pull-request-sha: f2ec265 Change-Id: Idec175b9c06274628d3d027024f9878abb1d188b
1 parent 0fe5b17 commit e3187d8

File tree

6 files changed

+60
-6
lines changed

6 files changed

+60
-6
lines changed

docs/build/changelog.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ Changelog
33
=========
44

55
.. changelog::
6-
:version: 1.1.9
6+
:version: 1.2.0
77
:include_notes_from: unreleased
88

99
.. changelog::

docs/build/unreleased/236.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.. change::
2+
:tags: feature, region
3+
:tickets: 236
4+
5+
Added new construct :class:`.api.CantDeserializeException` which can be
6+
raised by user-defined deserializer functions which would be passed to
7+
:paramref:`.CacheRegion.deserializer`, to indicate a cache value that can't
8+
be deserialized and therefore should be regenerated. This can allow an
9+
application that's been updated to gracefully re-cache old items that were
10+
persisted from a previous version of the application. Pull request courtesy
11+
Simon Hewitt.

dogpile/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "1.1.9"
1+
__version__ = "1.2.0"
22

33
from .lock import Lock # noqa
44
from .lock import NeedRegenerationException # noqa

dogpile/cache/api.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,15 @@ def __bool__(self): # pragma NO COVERAGE
5151
Deserializer = Callable[[bytes], ValuePayload]
5252

5353

54+
class CantDeserializeException(Exception):
55+
"""Exception indicating deserialization failed, and that caching
56+
should proceed to re-generate a value
57+
58+
.. versionadded:: 1.2.0
59+
60+
"""
61+
62+
5463
class CacheMutex(abc.ABC):
5564
"""Describes a mutexing object with acquire and release methods.
5665

dogpile/cache/region.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from .api import CachedValue
2828
from .api import CacheMutex
2929
from .api import CacheReturnType
30+
from .api import CantDeserializeException
3031
from .api import KeyType
3132
from .api import MetaDataType
3233
from .api import NO_VALUE
@@ -328,7 +329,17 @@ def generate_keys(*args):
328329
deserializer recommended by the backend will be used. Typical
329330
deserializers include ``pickle.dumps`` and ``json.dumps``.
330331
331-
.. versionadded:: 1.1.0
332+
Deserializers can raise a :class:`.api.CantDeserializeException` if they
333+
are unable to deserialize the value from the backend, indicating
334+
deserialization failed and that caching should proceed to re-generate
335+
a value. This allows an application that has been updated to gracefully
336+
re-cache old items which were persisted by a previous version of the
337+
application and can no longer be successfully deserialized.
338+
339+
.. versionadded:: 1.1.0 added "deserializer" parameter
340+
341+
.. versionadded:: 1.2.0 added support for
342+
:class:`.api.CantDeserializeException`
332343
333344
:param async_creation_runner: A callable that, when specified,
334345
will be passed to and called by dogpile.lock when
@@ -1219,8 +1230,12 @@ def _parse_serialized_from_backend(
12191230

12201231
bytes_metadata, _, bytes_payload = byte_value.partition(b"|")
12211232
metadata = json.loads(bytes_metadata)
1222-
payload = self.deserializer(bytes_payload)
1223-
return CachedValue(payload, metadata)
1233+
try:
1234+
payload = self.deserializer(bytes_payload)
1235+
except CantDeserializeException:
1236+
return NO_VALUE
1237+
else:
1238+
return CachedValue(payload, metadata)
12241239

12251240
def _serialize_cached_value_elements(
12261241
self, payload: ValuePayload, metadata: MetaDataType
@@ -1247,7 +1262,8 @@ def _serialized_payload(
12471262
return self._serialize_cached_value_elements(payload, metadata)
12481263

12491264
def _serialized_cached_value(self, value: CachedValue) -> BackendFormatted:
1250-
"""Return a backend formatted representation of a :class:`.CachedValue`.
1265+
"""Return a backend formatted representation of a
1266+
:class:`.CachedValue`.
12511267
12521268
If a serializer is in use then this will return a string representation
12531269
with the value formatted by the serializer.

tests/cache/_fixtures.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from dogpile.cache import register_backend
1515
from dogpile.cache.api import CacheBackend
1616
from dogpile.cache.api import CacheMutex
17+
from dogpile.cache.api import CantDeserializeException
1718
from dogpile.cache.api import NO_VALUE
1819
from dogpile.cache.region import _backend_loader
1920
from . import assert_raises_message
@@ -380,6 +381,10 @@ def boom():
380381
)
381382

382383

384+
def raise_cant_deserialize_exception(v):
385+
raise CantDeserializeException()
386+
387+
383388
class _GenericSerializerTest(TestCase):
384389
# Inheriting from this class will make test cases
385390
# use these serialization arguments
@@ -388,6 +393,19 @@ class _GenericSerializerTest(TestCase):
388393
"deserializer": json.loads,
389394
}
390395

396+
def test_serializer_cant_deserialize(self):
397+
region = self._region(
398+
region_args={
399+
"serializer": self.region_args["serializer"],
400+
"deserializer": raise_cant_deserialize_exception,
401+
}
402+
)
403+
404+
value = {"foo": ["bar", 1, False, None]}
405+
region.set("k", value)
406+
asserted = region.get("k")
407+
eq_(asserted, NO_VALUE)
408+
391409
def test_uses_serializer(self):
392410
region = self._region()
393411

0 commit comments

Comments
 (0)