diff options
author | Omer Katz <omer.drow@gmail.com> | 2021-04-01 13:48:00 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-04-01 13:48:00 +0300 |
commit | 560518287ab672fff3ca98107fb6bded456f6a01 (patch) | |
tree | 35d79e238d5572cef04445962d6dd6fc75ad5a34 | |
parent | 8474df91301cf4341d9baed0b2d6df2b4f3d17aa (diff) | |
download | kombu-560518287ab672fff3ca98107fb6bded456f6a01.tar.gz |
Use a thread-safe implementation of cached_property (#1316)
* Use a thread-safe implementation of cached_property.
* Restore setter and deleter.
* Restore tests.
* Fix __get__ signature for backport.
* Cleanup.
Co-authored-by: Asif Saif Uddin <auvipy@gmail.com>
-rw-r--r-- | kombu/utils/objects.py | 84 | ||||
-rw-r--r-- | requirements/default.txt | 1 | ||||
-rw-r--r-- | t/unit/utils/test_objects.py | 6 |
3 files changed, 41 insertions, 50 deletions
diff --git a/kombu/utils/objects.py b/kombu/utils/objects.py index 4f220439..faddb932 100644 --- a/kombu/utils/objects.py +++ b/kombu/utils/objects.py @@ -1,64 +1,54 @@ """Object Utilities.""" +__all__ = ('cached_property',) -class cached_property: - """Cached property descriptor. +try: + from functools import _NOT_FOUND, cached_property as _cached_property +except ImportError: + # TODO: Remove this fallback once we drop support for Python < 3.8 + from cached_property import threaded_cached_property as _cached_property - Caches the return value of the get method on first call. + _NOT_FOUND = object() - Examples: - .. code-block:: python - @cached_property - def connection(self): - return Connection() - - @connection.setter # Prepares stored value - def connection(self, value): - if value is None: - raise TypeError('Connection must be a connection') - return value - - @connection.deleter - def connection(self, value): - # Additional action to do at del(self.attr) - if value is not None: - print('Connection {0!r} deleted'.format(value) - """ - - def __init__(self, fget=None, fset=None, fdel=None, doc=None): - self.__get = fget +class cached_property(_cached_property): + def __init__(self, fget=None, fset=None, fdel=None): + super().__init__(fget) self.__set = fset self.__del = fdel - self.__doc__ = doc or fget.__doc__ - self.__name__ = fget.__name__ - self.__module__ = fget.__module__ - def __get__(self, obj, type=None): - if obj is None: - return self - try: - return obj.__dict__[self.__name__] - except KeyError: - value = obj.__dict__[self.__name__] = self.__get(obj) - return value + if not hasattr(self, 'attrname'): + # This is a backport so we set this ourselves. + self.attrname = self.func.__name__ + + def __get__(self, instance, owner=None): + # TODO: Remove this after we drop support for Python<3.8 + # or fix the signature in the cached_property package + return super().__get__(instance, owner) - def __set__(self, obj, value): - if obj is None: + def __set__(self, instance, value): + if instance is None: return self - if self.__set is not None: - value = self.__set(obj, value) - obj.__dict__[self.__name__] = value - def __delete__(self, obj, _sentinel=object()): - if obj is None: + with self.lock: + if self.__set is not None: + value = self.__set(instance, value) + + cache = instance.__dict__ + cache[self.attrname] = value + + def __delete__(self, instance): + if instance is None: return self - value = obj.__dict__.pop(self.__name__, _sentinel) - if self.__del is not None and value is not _sentinel: - self.__del(obj, value) + + with self.lock: + value = instance.__dict__.pop(self.attrname, _NOT_FOUND) + + if self.__del and value is not _NOT_FOUND: + self.__del(instance, value) def setter(self, fset): - return self.__class__(self.__get, fset, self.__del) + return self.__class__(self.func, fset, self.__del) def deleter(self, fdel): - return self.__class__(self.__get, self.__set, fdel) + return self.__class__(self.func, self.__set, fdel) diff --git a/requirements/default.txt b/requirements/default.txt index eac21d82..4d27a499 100644 --- a/requirements/default.txt +++ b/requirements/default.txt @@ -1,3 +1,4 @@ importlib-metadata>=0.18; python_version<"3.8" +cached_property; python_version<"3.8" amqp>=5.0.6,<6.0.0 vine diff --git a/t/unit/utils/test_objects.py b/t/unit/utils/test_objects.py index a1ad2000..330a7db5 100644 --- a/t/unit/utils/test_objects.py +++ b/t/unit/utils/test_objects.py @@ -17,10 +17,10 @@ class test_cached_property: self.xx = value x = X() - del(x.foo) + del x.foo assert not x.xx x.__dict__['foo'] = 'here' - del(x.foo) + del x.foo assert x.xx == 'here' def test_when_access_from_class(self): @@ -48,4 +48,4 @@ class test_cached_property: x.foo = 30 assert x.xx == 10 - del(x.foo) + del x.foo |