From 5f43d4232538d06f7b763b2e209087ce26af4702 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ma=C5=9Blanka?= <pmaslanka@smok.co> Date: Fri, 15 Oct 2021 16:51:41 +0200 Subject: [PATCH] v2.18.5 --- CHANGELOG.md | 2 +- docs/coding/decorators.rst | 2 ++ satella/__init__.py | 2 +- satella/coding/decorators/__init__.py | 6 ++-- satella/coding/decorators/arguments.py | 39 ++++++++++++++++++++++++++ tests/test_coding/test_decorators.py | 19 ++++++++++++- 6 files changed, 65 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f5e7651..d00e5698 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,3 @@ # v2.18.5 - +* added `cached_property` diff --git a/docs/coding/decorators.rst b/docs/coding/decorators.rst index 52020b49..ffa3727f 100644 --- a/docs/coding/decorators.rst +++ b/docs/coding/decorators.rst @@ -17,6 +17,8 @@ Decorators .. autofunction:: satella.coding.decorators.cache_memoize +.. autofunction:: satella.coding.decorators.call_with_arguments + .. autofunction:: satella.coding.decorators.queue_get .. autofunction:: satella.coding.decorators.copy_arguments diff --git a/satella/__init__.py b/satella/__init__.py index 55439584..8f1a6377 100644 --- a/satella/__init__.py +++ b/satella/__init__.py @@ -1 +1 @@ -__version__ = '2.18.5a1' +__version__ = '2.18.5' diff --git a/satella/coding/decorators/__init__.py b/satella/coding/decorators/__init__.py index 3fc063c0..f524f8fa 100644 --- a/satella/coding/decorators/__init__.py +++ b/satella/coding/decorators/__init__.py @@ -1,6 +1,7 @@ from .arguments import auto_adapt_to_methods, attach_arguments, for_argument, \ execute_before, copy_arguments, replace_argument_if, transform_result, \ - transform_arguments, execute_if_attribute_none, execute_if_attribute_not_none + transform_arguments, execute_if_attribute_none, execute_if_attribute_not_none, \ + cached_property from .decorators import wraps, chain_functions, has_keys, short_none, memoize, return_as_list, \ default_return, cache_memoize, call_method_on_exception from .flow_control import loop_while, queue_get @@ -13,4 +14,5 @@ __all__ = ['retry', 'transform_result', 'transform_arguments', 'attach_arguments', 'for_argument', 'loop_while', 'memoize', 'copy_arguments', 'replace_argument_if', 'return_as_list', 'default_return', 'cache_memoize', 'call_method_on_exception', - 'execute_if_attribute_none', 'execute_if_attribute_not_none'] + 'execute_if_attribute_none', 'execute_if_attribute_not_none', + 'cached_property'] diff --git a/satella/coding/decorators/arguments.py b/satella/coding/decorators/arguments.py index e7fa1308..8e33ad4b 100644 --- a/satella/coding/decorators/arguments.py +++ b/satella/coding/decorators/arguments.py @@ -410,3 +410,42 @@ def execute_if_attribute_not_none(attribute: str): return inner return outer + +def cached_property(prop_name: str, assume_not_loaded = None): + """ + A decorator to use to create cached properties. + + You job is to only write the value returner. If the value is + currently assume_not_loaded (None by default) your property + method will be called. Otherwise it will be served from + cached attribute, whose value you provide as parameter. + + Use as follows: + + >>> class Example: + >>> def __init__(self): + >>> self._a = None + >>> @property + >>> @cached_property('_a') + >>> def a(self) -> str: + >>> return 'abc' + >>> a = Example() + >>> assert a.a == 'abc' + >>> assert a._a == 'abc' + + :param prop_name: Name of property to store the value in + :param assume_not_loaded: Value if currently the attribute is + equal to this, it is assumed to not have been loaded + """ + def outer(fun): + @wraps(fun) + def inner(self, *args, **kwargs): + attr_v = getattr(self, prop_name) + if attr_v == assume_not_loaded: + attr_v = fun(self, *args, **kwargs) + setattr(self, prop_name, attr_v) + return attr_v + else: + return attr_v + return inner + return outer diff --git a/tests/test_coding/test_decorators.py b/tests/test_coding/test_decorators.py index 425f384f..cb69d32b 100644 --- a/tests/test_coding/test_decorators.py +++ b/tests/test_coding/test_decorators.py @@ -10,7 +10,7 @@ from satella.coding.decorators import auto_adapt_to_methods, attach_arguments, \ execute_before, loop_while, memoize, copy_arguments, replace_argument_if, \ retry, return_as_list, default_return, transform_result, transform_arguments, \ cache_memoize, call_method_on_exception, execute_if_attribute_none, \ - execute_if_attribute_not_none + execute_if_attribute_not_none, cached_property from satella.coding.predicates import x from satella.exceptions import PreconditionError @@ -19,6 +19,23 @@ logger = logging.getLogger(__name__) class TestDecorators(unittest.TestCase): + def test_cached_property(self): + class Example: + def __init__(self): + self._a = None + self.called = 0 + + @property + @cached_property('_a') + def a(self): + self.called += 1 + return 'abc' + a = Example() + self.assertEqual(a.a, 'abc') + self.assertEqual(a.called, 1) + self.assertEqual(a.a, 'abc') + self.assertEqual(a.called, 1) + def test_execute_if_attribute_not_none(self): class ExecIfAttrNone: def __init__(self): -- GitLab