Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

handle CantDeserializeException raised from deserialize method #236

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions dogpile/cache/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@ def __bool__(self): # pragma NO COVERAGE
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.

Expand Down
23 changes: 19 additions & 4 deletions dogpile/cache/region.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
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
Expand Down Expand Up @@ -328,7 +329,16 @@ def generate_keys(*args):
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
sjhewitt marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down Expand Up @@ -1219,8 +1229,12 @@ def _parse_serialized_from_backend(

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
Expand All @@ -1247,7 +1261,8 @@ def _serialized_payload(
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.
Expand Down
18 changes: 18 additions & 0 deletions tests/cache/_fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
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
Expand Down Expand Up @@ -380,6 +381,10 @@ def boom():
)


def raise_cant_deserialize_exception(v):
raise CantDeserializeException()


class _GenericSerializerTest(TestCase):
# Inheriting from this class will make test cases
# use these serialization arguments
Expand All @@ -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()

Expand Down