summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Hewitt <simon@archera.ai>2023-04-18 18:42:59 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2023-04-25 13:15:23 -0400
commite3187d80896078fdd03377a374978f42e8ad28ec (patch)
tree569171014b9e161fc4cc36b736ad9cf372a4a5e1
parent0fe5b17957a4417fa39ccd038bd861c097d5b896 (diff)
downloaddogpile-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.rst2
-rw-r--r--docs/build/unreleased/236.rst11
-rw-r--r--dogpile/__init__.py2
-rw-r--r--dogpile/cache/api.py9
-rw-r--r--dogpile/cache/region.py24
-rw-r--r--tests/cache/_fixtures.py18
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()