From 748d3e761b0df10660237962738471764b7deabc Mon Sep 17 00:00:00 2001 From: Tjeerd Verschragen Date: Tue, 1 Apr 2025 15:01:27 +0200 Subject: [PATCH 1/2] Add revalidate_fn optional param to cached_result - revalidate_fn: Callable function that returns bool whether to revalidate the cached result. - update unit tests. --- nwastdlib/asyncio_cache.py | 25 +++++++++++++-------- setup.cfg | 2 +- tests/test_asyncio_cache.py | 44 +++++++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 10 deletions(-) diff --git a/nwastdlib/asyncio_cache.py b/nwastdlib/asyncio_cache.py index 6ac7338..997b267 100644 --- a/nwastdlib/asyncio_cache.py +++ b/nwastdlib/asyncio_cache.py @@ -118,6 +118,7 @@ def cached_result( key_name: str | None = None, expiry_seconds: int = 120, serializer: SerializerProtocol = DefaultSerializer, + revalidate_fn: Callable[..., bool] | None = None, ) -> Callable: """Pass returned result objects from a async function call into redis. @@ -150,6 +151,7 @@ def my_other_function... key_name: When specified the total redis key_name will be "prefix:key_name". expiry_seconds: expiration in seconds. Defaults to two minutes (120s). serializer: Provide your own Serializer class or use the default pickle. + revalidate_fn: Callable function that returns bool whether to revalidate the cached result. Returns: decorator function @@ -159,6 +161,8 @@ def my_other_function... def cache_decorator(func: Callable) -> Callable: @wraps(func) async def func_wrapper(*args: tuple[Any], **kwargs: dict[str, Any]) -> Any: + from_cache = (not revalidate_fn(*args, **kwargs)) if revalidate_fn else True + python_major, python_minor = sys.version_info[:2] if key_name: cache_key = f"{prefix}:{python_major}.{python_minor}:{key_name}" @@ -169,17 +173,20 @@ async def func_wrapper(*args: tuple[Any], **kwargs: dict[str, Any]) -> Any: cache_key = f"{prefix}:{python_major}.{python_minor}:{func.__name__}{args_and_kwargs_string}" logger.debug("Autogenerated a cache key", cache_key=cache_key) - logger.debug("Cache called with wrapper func", func_name=func.__name__, cache_key=cache_key) - if secret: - if (cached_val := await get_signed_cache_value(pool, secret, cache_key, serializer)) is not None: - logger.info("Cache contains secure key, serving from cache", func_name=func.__name__) - return cached_val + if from_cache: + logger.debug("Cache called with wrapper func", func_name=func.__name__, cache_key=cache_key) + if secret: + if (cached_val := await get_signed_cache_value(pool, secret, cache_key, serializer)) is not None: + logger.info("Cache contains secure key, serving from cache", func_name=func.__name__) + return cached_val + else: + if (cached_val := await get_cache_value(pool, cache_key, serializer)) is not None: + logger.info("Cache contains key, serving from cache", func_name=func.__name__) + return cached_val + logger.info("Cache doesn't contain key, calling real function", func_name=func.__name__) else: - if (cached_val := await get_cache_value(pool, cache_key, serializer)) is not None: - logger.info("Cache contains key, serving from cache", func_name=func.__name__) - return cached_val + logger.info("ignoring cache, calling real function", func_name=func.__name__) - logger.info("Cache doesn't contain key, calling real function", func_name=func.__name__) result = await func(*args, **kwargs) if secret: await set_signed_cache_value(pool, secret, cache_key, result, expiry_seconds, serializer) diff --git a/setup.cfg b/setup.cfg index c4afdc2..22f3a9a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -31,7 +31,7 @@ strict_equality = True show_error_codes = True show_column_numbers = True -exclude = (benchmarks/*) +exclude = (benchmarks/*|tests/*) ;lineprecision_report = mypy-coverage diff --git a/tests/test_asyncio_cache.py b/tests/test_asyncio_cache.py index 44b5b0a..830f60e 100644 --- a/tests/test_asyncio_cache.py +++ b/tests/test_asyncio_cache.py @@ -176,3 +176,47 @@ async def slow_function(): # # A new call should still serve 0: as it is cached now result = await slow_function() assert result == original_value + + +async def test_cache_decorator_with_revalidation_fn(): + redis = FakeRedis() + value = 0 + + def revalidate_fn(*args, **kwargs): + return kwargs["revalidate_cache"] + + @cached_result(redis, "test-suite", "SECRETNAME", "keyname", revalidate_fn=revalidate_fn) + async def slow_function(revalidate_cache: bool): + return value + + result = await slow_function(revalidate_cache=False) + assert result == 0 + + # change the value so we can verify that the function was not called + value = 1 + + # A new call should still serve 0: as it is cached now + result = await slow_function(revalidate_cache=False) + assert result == 0 + + +async def test_cache_decorator_with_revalidation_fn_no_cache(): + redis = FakeRedis() + value = 0 + + def revalidate_fn(*args, **kwargs): + return kwargs["revalidate_cache"] + + @cached_result(redis, "test-suite", "SECRETNAME", "keyname", revalidate_fn=revalidate_fn) + async def slow_function(revalidate_cache: bool): + return value + + result = await slow_function(revalidate_cache=True) + assert result == 0 + + # change the value so we can verify that the function was called + value = 1 + + # A new call should serve 1: as it is not cached now + result = await slow_function(revalidate_cache=True) + assert result == 1 From a4648f33155a1e5816e11085ce72551f17785c56 Mon Sep 17 00:00:00 2001 From: Tjeerd Verschragen Date: Tue, 1 Apr 2025 15:51:10 +0200 Subject: [PATCH 2/2] Bumpversion to 1.9.2 --- .bumpversion.cfg | 2 +- nwastdlib/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index bb91963..1c6c0f5 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 1.9.1 +current_version = 1.9.2 commit = False tag = False parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\-(?P[a-z]+)(?P\d+))? diff --git a/nwastdlib/__init__.py b/nwastdlib/__init__.py index f9d7b49..769e18c 100644 --- a/nwastdlib/__init__.py +++ b/nwastdlib/__init__.py @@ -13,7 +13,7 @@ # """The NWA-stdlib module.""" -__version__ = "1.9.1" +__version__ = "1.9.2" from nwastdlib.f import const, identity