New Upstream Release - python-dogpile.cache
Ready changes
Summary
Merged new upstream version: 1.2.2 (was: 1.2.1).
Resulting package
Built on 2023-08-21T09:02 (took 8m6s)
The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:
apt install -t fresh-releases python-dogpile.cache-docapt install -t fresh-releases python3-dogpile.cache
Lintian Result
Diff
diff --git a/LICENSE b/LICENSE
index ff6e570..693219c 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright 2005-2022 Michael Bayer.
+Copyright 2005-2023 Michael Bayer.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/debian/changelog b/debian/changelog
index ee87ca2..ad80903 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,10 @@
+python-dogpile.cache (1.2.2-1) UNRELEASED; urgency=low
+
+ * New upstream release.
+ * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk> Mon, 21 Aug 2023 08:55:03 -0000
+
python-dogpile.cache (1.1.8-2) unstable; urgency=medium
* Uploading to unstable.
diff --git a/debian/patches/no-intersphinx.patch b/debian/patches/no-intersphinx.patch
index a629092..b1f1c69 100644
--- a/debian/patches/no-intersphinx.patch
+++ b/debian/patches/no-intersphinx.patch
@@ -4,9 +4,11 @@ Author: Thomas Goirand <zigo@debian.org>
Forwarded: no
Last-Update: 2020-09-02
---- a/docs/build/conf.py 2020-09-02 13:39:55.857891290 +0200
-+++ b/docs/build/conf.py 2020-09-02 13:40:22.081832983 +0200
-@@ -34,7 +34,6 @@
+Index: python-dogpile.cache.git/docs/build/conf.py
+===================================================================
+--- python-dogpile.cache.git.orig/docs/build/conf.py
++++ python-dogpile.cache.git/docs/build/conf.py
+@@ -34,7 +34,6 @@ if True:
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
"sphinx.ext.autodoc",
diff --git a/debian/patches/remove-non-deterministic-tests.patch b/debian/patches/remove-non-deterministic-tests.patch
index 07cbd42..f2f6df6 100644
--- a/debian/patches/remove-non-deterministic-tests.patch
+++ b/debian/patches/remove-non-deterministic-tests.patch
@@ -5,11 +5,11 @@ Bug-Debian: https://bugs.debian.org/861173
Forwarded: no
Last-Update: 2020-03-07
-Index: python-dogpile.cache/tests/cache/test_memcached_backend.py
+Index: python-dogpile.cache.git/tests/cache/test_memcached_backend.py
===================================================================
---- python-dogpile.cache.orig/tests/cache/test_memcached_backend.py
-+++ python-dogpile.cache/tests/cache/test_memcached_backend.py
-@@ -327,44 +327,3 @@ class MemcachedArgstest(TestCase):
+--- python-dogpile.cache.git.orig/tests/cache/test_memcached_backend.py
++++ python-dogpile.cache.git/tests/cache/test_memcached_backend.py
+@@ -498,44 +498,3 @@ class MemcachedArgstest(TestCase):
backend.set("foo", "bar")
eq_(backend._clients.memcached.canary, [{"min_compress_len": 20}])
@@ -54,10 +54,10 @@ Index: python-dogpile.cache/tests/cache/test_memcached_backend.py
-
- gc.collect()
- eq_(MockClient.number_of_clients(), 0)
-Index: python-dogpile.cache/tests/test_lock.py
+Index: python-dogpile.cache.git/tests/test_lock.py
===================================================================
---- python-dogpile.cache.orig/tests/test_lock.py
-+++ python-dogpile.cache/tests/test_lock.py
+--- python-dogpile.cache.git.orig/tests/test_lock.py
++++ python-dogpile.cache.git/tests/test_lock.py
@@ -24,10 +24,6 @@ class ConcurrencyTest(TestCase):
def test_quick(self):
self._test_multi(10, 2, 0.5, 50, 0.05, 0.1)
diff --git a/docs/build/_static/nature_override.css b/docs/build/_static/nature_override.css
index 33c0d05..57798ad 100644
--- a/docs/build/_static/nature_override.css
+++ b/docs/build/_static/nature_override.css
@@ -14,3 +14,11 @@
font-style: italic;
}
+div.documentwrapper div.bodywrapper { margin-left: 350px;}
+div.document div.sphinxsidebar { width: 350px; }
+
+div.sphinxsidebarwrapper div {
+ overflow: auto;
+}
+
+
diff --git a/docs/build/changelog.rst b/docs/build/changelog.rst
index 74c1b6d..98b8235 100644
--- a/docs/build/changelog.rst
+++ b/docs/build/changelog.rst
@@ -2,6 +2,45 @@
Changelog
=========
+.. changelog::
+ :version: 1.2.2
+ :released: Sat Jul 8 2023
+
+ .. change::
+ :tags: bug, typing
+ :tickets: 240
+
+ Made use of pep-673 ``Self`` type for method chained methods such as
+ :meth:`.CacheRegion.configure` and :meth:`.ProxyBackend.wrap`. Pull request
+ courtesy Viicos.
+
+.. changelog::
+ :version: 1.2.1
+ :released: Sat May 20 2023
+
+ .. change::
+ :tags: bug, typing
+ :tickets: 238
+
+ Added py.typed file to root so that typing tools such as Mypy recognize
+ dogpile as typed. Pull request courtesy Daverball.
+
+.. changelog::
+ :version: 1.2.0
+ :released: Wed Apr 26 2023
+
+ .. 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.
+
.. changelog::
:version: 1.1.8
:released: Fri Jul 8 2022
diff --git a/docs/build/conf.py b/docs/build/conf.py
index 4276787..a0580f6 100644
--- a/docs/build/conf.py
+++ b/docs/build/conf.py
@@ -65,7 +65,7 @@ master_doc = "index"
# General information about the project.
project = "dogpile.cache"
-copyright = "2011-2022 Mike Bayer"
+copyright = "2011-2023 Mike Bayer"
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
@@ -74,7 +74,7 @@ copyright = "2011-2022 Mike Bayer"
# The short X.Y version.
version = dogpile.__version__
# The full version, including alpha/beta/rc tags.
-release = "1.1.8"
+release = "1.2.2"
# The language for content autogenerated by Sphinx. Refer to documentation
diff --git a/dogpile/__init__.py b/dogpile/__init__.py
index 2367ae0..e4b57bf 100644
--- a/dogpile/__init__.py
+++ b/dogpile/__init__.py
@@ -1,4 +1,4 @@
-__version__ = "1.1.8"
+__version__ = "1.2.2"
from .lock import Lock # noqa
from .lock import NeedRegenerationException # noqa
diff --git a/dogpile/cache/api.py b/dogpile/cache/api.py
index 0717d43..d8436ca 100644
--- a/dogpile/cache/api.py
+++ b/dogpile/cache/api.py
@@ -9,6 +9,8 @@ from typing import Optional
from typing import Sequence
from typing import Union
+from ..util.typing import Self
+
class NoValue:
"""Describe a missing cache value.
@@ -18,7 +20,7 @@ class NoValue:
"""
@property
- def payload(self):
+ def payload(self) -> Self:
return self
def __repr__(self):
@@ -51,6 +53,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.
@@ -181,7 +192,9 @@ class CacheBackend:
raise NotImplementedError()
@classmethod
- def from_config_dict(cls, config_dict, prefix):
+ def from_config_dict(
+ cls, config_dict: Mapping[str, Any], prefix: str
+ ) -> Self:
prefix_len = len(prefix)
return cls(
dict(
diff --git a/dogpile/cache/proxy.py b/dogpile/cache/proxy.py
index bf6e296..cd6120b 100644
--- a/dogpile/cache/proxy.py
+++ b/dogpile/cache/proxy.py
@@ -9,7 +9,6 @@ base backend.
.. versionadded:: 0.5.0 Added support for the :class:`.ProxyBackend` class.
"""
-
from typing import Mapping
from typing import Optional
from typing import Sequence
@@ -20,6 +19,7 @@ from .api import CacheBackend
from .api import CacheMutex
from .api import KeyType
from .api import SerializedReturnType
+from ..util.typing import Self
class ProxyBackend(CacheBackend):
@@ -67,7 +67,7 @@ class ProxyBackend(CacheBackend):
def __init__(self, *arg, **kw):
pass
- def wrap(self, backend: CacheBackend) -> "ProxyBackend":
+ def wrap(self, backend: CacheBackend) -> Self:
"""Take a backend as an argument and setup the self.proxied property.
Return an object that be used as a backend by a :class:`.CacheRegion`
object.
diff --git a/dogpile/cache/region.py b/dogpile/cache/region.py
index ef0dbc4..7952157 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
@@ -45,6 +46,7 @@ from ..util import coerce_string_conf
from ..util import memoized_property
from ..util import NameRegistry
from ..util import PluginLoader
+from ..util.typing import Self
value_version = 2
"""An integer placed in the :class:`.CachedValue`
@@ -328,7 +330,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
@@ -415,7 +427,7 @@ class CacheRegion:
wrap: Sequence[Union[ProxyBackend, Type[ProxyBackend]]] = (),
replace_existing_backend: bool = False,
region_invalidator: Optional[RegionInvalidationStrategy] = None,
- ) -> "CacheRegion":
+ ) -> Self:
"""Configure a :class:`.CacheRegion`.
The :class:`.CacheRegion` itself
@@ -1219,8 +1231,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 +1263,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/dogpile/py.typed b/dogpile/py.typed
new file mode 100644
index 0000000..e69de29
diff --git a/dogpile/util/typing.py b/dogpile/util/typing.py
new file mode 100644
index 0000000..218c429
--- /dev/null
+++ b/dogpile/util/typing.py
@@ -0,0 +1,6 @@
+import sys
+
+if sys.version_info >= (3, 11):
+ from typing import Self
+else:
+ from typing_extensions import Self # noqa: F401
diff --git a/setup.cfg b/setup.cfg
index 319c4b9..1818a23 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -24,9 +24,11 @@ project_urls =
install_requires =
decorator>=4.0.0
stevedore>=3.0.0
+ typing_extensions>=4.0.1;python_version<'3.11'
zip_safe = False
packages = find:
python_requires = >=3.6
+include_package_data = True
package_dir =
=.
@@ -38,6 +40,9 @@ exclude =
[options.exclude_package_data]
'' = tests*
+[options.package_data]
+* = py.typed
+
[options.entry_points]
mako.cache =
dogpile.cache = dogpile.cache.plugins.mako_cache:MakoPlugin
diff --git a/tests/cache/_fixtures.py b/tests/cache/_fixtures.py
index 9e71f8f..fd4688a 100644
--- a/tests/cache/_fixtures.py
+++ b/tests/cache/_fixtures.py
@@ -14,12 +14,17 @@ 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
from . import eq_
+def gen_some_key():
+ return f"some_key_{random.randint(1, 100000)}"
+
+
class _GenericBackendFixture(object):
@classmethod
def setup_class(cls):
@@ -32,12 +37,13 @@ class _GenericBackendFixture(object):
cls._check_backend_available(backend)
def tearDown(self):
+ some_key = gen_some_key()
if self._region_inst:
for key in self._keys:
self._region_inst.delete(key)
self._keys.clear()
elif self._backend_inst:
- self._backend_inst.delete("some_key")
+ self._backend_inst.delete(some_key)
@classmethod
def _check_backend_available(cls, backend):
@@ -93,22 +99,26 @@ class _GenericBackendFixture(object):
class _GenericBackendTest(_GenericBackendFixture, TestCase):
def test_backend_get_nothing(self):
backend = self._backend()
- eq_(backend.get_serialized("some_key"), NO_VALUE)
+ some_key = gen_some_key()
+ eq_(backend.get_serialized(some_key), NO_VALUE)
def test_backend_delete_nothing(self):
backend = self._backend()
- backend.delete("some_key")
+ some_key = gen_some_key()
+ backend.delete(some_key)
def test_backend_set_get_value(self):
backend = self._backend()
- backend.set_serialized("some_key", b"some value")
- eq_(backend.get_serialized("some_key"), b"some value")
+ some_key = gen_some_key()
+ backend.set_serialized(some_key, b"some value")
+ eq_(backend.get_serialized(some_key), b"some value")
def test_backend_delete(self):
backend = self._backend()
- backend.set_serialized("some_key", b"some value")
- backend.delete("some_key")
- eq_(backend.get_serialized("some_key"), NO_VALUE)
+ some_key = gen_some_key()
+ backend.set_serialized(some_key, b"some value")
+ backend.delete(some_key)
+ eq_(backend.get_serialized(some_key), NO_VALUE)
def test_region_is_key_locked(self):
reg = self._region()
@@ -127,8 +137,9 @@ class _GenericBackendTest(_GenericBackendFixture, TestCase):
def test_region_set_get_value(self):
reg = self._region()
- reg.set("some key", "some value")
- eq_(reg.get("some key"), "some value")
+ some_key = gen_some_key()
+ reg.set(some_key, "some value")
+ eq_(reg.get(some_key), "some value")
def test_region_set_multiple_values(self):
reg = self._region()
@@ -201,8 +212,9 @@ class _GenericBackendTest(_GenericBackendFixture, TestCase):
def test_region_set_get_nothing(self):
reg = self._region()
- reg.delete_multi(["some key"])
- eq_(reg.get("some key"), NO_VALUE)
+ some_key = gen_some_key()
+ reg.delete_multi([some_key])
+ eq_(reg.get(some_key), NO_VALUE)
def test_region_creator(self):
reg = self._region()
@@ -210,7 +222,8 @@ class _GenericBackendTest(_GenericBackendFixture, TestCase):
def creator():
return "some value"
- eq_(reg.get_or_create("some key", creator), "some value")
+ some_key = gen_some_key()
+ eq_(reg.get_or_create(some_key, creator), "some value")
@pytest.mark.time_intensive
def test_threaded_dogpile(self):
@@ -220,6 +233,7 @@ class _GenericBackendTest(_GenericBackendFixture, TestCase):
reg = self._region(config_args={"expiration_time": 0.25})
lock = Lock()
canary = []
+ some_key = gen_some_key()
def creator():
ack = lock.acquire(False)
@@ -231,7 +245,7 @@ class _GenericBackendTest(_GenericBackendFixture, TestCase):
def f():
for x in range(5):
- reg.get_or_create("some key", creator)
+ reg.get_or_create(some_key, creator)
time.sleep(0.5)
threads = [Thread(target=f) for i in range(10)]
@@ -252,8 +266,9 @@ class _GenericBackendTest(_GenericBackendFixture, TestCase):
With "distributed" locks, this is not 100% the case.
"""
+ some_key = gen_some_key()
reg = self._region(config_args={"expiration_time": 0.25})
- backend_mutex = reg.backend.get_mutex("some_key")
+ backend_mutex = reg.backend.get_mutex(some_key)
is_custom_mutex = backend_mutex is not None
locks = dict((str(i), Lock()) for i in range(11))
@@ -309,10 +324,11 @@ class _GenericBackendTest(_GenericBackendFixture, TestCase):
def test_region_delete(self):
reg = self._region()
- reg.set("some key", "some value")
- reg.delete("some key")
- reg.delete("some key")
- eq_(reg.get("some key"), NO_VALUE)
+ some_key = gen_some_key()
+ reg.set(some_key, "some value")
+ reg.delete(some_key)
+ reg.delete(some_key)
+ eq_(reg.get(some_key), NO_VALUE)
@pytest.mark.time_intensive
def test_region_expire(self):
@@ -323,6 +339,7 @@ class _GenericBackendTest(_GenericBackendFixture, TestCase):
# with very slow processing missing a timeout, as is often the
# case with this particular test
+ some_key = gen_some_key()
expire_time = 1.00
reg = self._region(config_args={"expiration_time": expire_time})
@@ -331,18 +348,18 @@ class _GenericBackendTest(_GenericBackendFixture, TestCase):
def creator():
return "some value %d" % next(counter)
- eq_(reg.get_or_create("some key", creator), "some value 1")
+ eq_(reg.get_or_create(some_key, creator), "some value 1")
time.sleep(expire_time + (0.2 * expire_time))
# expiration is definitely hit
- post_expiration = reg.get("some key", ignore_expiration=True)
+ post_expiration = reg.get(some_key, ignore_expiration=True)
if post_expiration is not NO_VALUE:
eq_(post_expiration, "some value 1")
- eq_(reg.get_or_create("some key", creator), "some value 2")
+ eq_(reg.get_or_create(some_key, creator), "some value 2")
# this line needs to run less the expire_time sec before the previous
# two or it hits the expiration
- eq_(reg.get("some key"), "some value 2")
+ eq_(reg.get(some_key), "some value 2")
def test_decorated_fn_functionality(self):
# test for any quirks in the fn decoration that interact
@@ -370,16 +387,21 @@ class _GenericBackendTest(_GenericBackendFixture, TestCase):
eq_(my_function(4, 3), 11)
def test_exploding_value_fn(self):
+ some_key = gen_some_key()
reg = self._region()
def boom():
raise Exception("boom")
assert_raises_message(
- Exception, "boom", reg.get_or_create, "some_key", boom
+ Exception, "boom", reg.get_or_create, some_key, boom
)
+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 +410,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()
Debdiff
[The following lists of changes regard files as different if they have different names, permissions or owners.]
Files in second set of .debs but not in first
-rw-r--r-- root/root /usr/lib/python3/dist-packages/dogpile.cache-1.2.2.egg-info/PKG-INFO -rw-r--r-- root/root /usr/lib/python3/dist-packages/dogpile.cache-1.2.2.egg-info/dependency_links.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/dogpile.cache-1.2.2.egg-info/entry_points.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/dogpile.cache-1.2.2.egg-info/not-zip-safe -rw-r--r-- root/root /usr/lib/python3/dist-packages/dogpile.cache-1.2.2.egg-info/requires.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/dogpile.cache-1.2.2.egg-info/top_level.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/dogpile/py.typed -rw-r--r-- root/root /usr/lib/python3/dist-packages/dogpile/util/typing.py lrwxrwxrwx root/root /usr/share/doc/python-dogpile.cache-doc/html/_static/_sphinx_javascript_frameworks_compat.js -> ../../../../javascript/sphinxdoc/1.0/_sphinx_javascript_frameworks_compat.js lrwxrwxrwx root/root /usr/share/doc/python-dogpile.cache-doc/html/_static/sphinx_highlight.js -> ../../../../javascript/sphinxdoc/1.0/sphinx_highlight.js
Files in first set of .debs but not in second
-rw-r--r-- root/root /usr/lib/python3/dist-packages/dogpile.cache-1.1.8.egg-info/PKG-INFO -rw-r--r-- root/root /usr/lib/python3/dist-packages/dogpile.cache-1.1.8.egg-info/dependency_links.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/dogpile.cache-1.1.8.egg-info/entry_points.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/dogpile.cache-1.1.8.egg-info/not-zip-safe -rw-r--r-- root/root /usr/lib/python3/dist-packages/dogpile.cache-1.1.8.egg-info/requires.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/dogpile.cache-1.1.8.egg-info/top_level.txt
Control files of package python-dogpile.cache-doc: lines which differ (wdiff format)
Depends: libjs-sphinxdoc (>= 4.3) 5.2)
Control files of package python3-dogpile.cache: lines which differ (wdiff format)
Depends: python3-decorator, python3-mako, python3-stevedore, python3-typing-extensions | python3-supported-min (>> 3.11), python3:any