compat.py 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. # SPDX-License-Identifier: AGPL-3.0-or-later
  2. # lint: pylint
  3. # pyright: basic
  4. """Module for backward compatibility.
  5. """
  6. # pylint: disable=C,R
  7. __all__ = ('cached_property',)
  8. try:
  9. from functools import cached_property # type: ignore
  10. except ImportError:
  11. # cache_property has been added in py3.8 [1]
  12. #
  13. # To support cache_property in py3.7 the implementation from 3.8 has been
  14. # copied here. This code can be cleanup with EOL of py3.7.
  15. #
  16. # [1] https://docs.python.org/3/library/functools.html#functools.cached_property
  17. from threading import RLock
  18. _NOT_FOUND = object()
  19. class cached_property:
  20. def __init__(self, func):
  21. self.func = func
  22. self.attrname = None
  23. self.__doc__ = func.__doc__
  24. self.lock = RLock()
  25. def __set_name__(self, owner, name):
  26. if self.attrname is None:
  27. self.attrname = name
  28. elif name != self.attrname:
  29. raise TypeError(
  30. "Cannot assign the same cached_property to two different names "
  31. f"({self.attrname!r} and {name!r})."
  32. )
  33. def __get__(self, instance, owner=None):
  34. if instance is None:
  35. return self
  36. if self.attrname is None:
  37. raise TypeError("Cannot use cached_property instance without calling __set_name__ on it.")
  38. try:
  39. cache = instance.__dict__
  40. except AttributeError: # not all objects have __dict__ (e.g. class defines slots)
  41. msg = (
  42. f"No '__dict__' attribute on {type(instance).__name__!r} "
  43. f"instance to cache {self.attrname!r} property."
  44. )
  45. raise TypeError(msg) from None
  46. val = cache.get(self.attrname, _NOT_FOUND)
  47. if val is _NOT_FOUND:
  48. with self.lock:
  49. # check if another thread filled cache while we awaited lock
  50. val = cache.get(self.attrname, _NOT_FOUND)
  51. if val is _NOT_FOUND:
  52. val = self.func(instance)
  53. try:
  54. cache[self.attrname] = val
  55. except TypeError:
  56. msg = (
  57. f"The '__dict__' attribute on {type(instance).__name__!r} instance "
  58. f"does not support item assignment for caching {self.attrname!r} property."
  59. )
  60. raise TypeError(msg) from None
  61. return val