From 7ec27a132c6a3ee324cd02bdfdb6132870c6ba80 Mon Sep 17 00:00:00 2001 From: amirreza Date: Sat, 1 Nov 2025 20:33:43 +0330 Subject: [PATCH 1/7] use `dir` instead of `vars` to get inherited methods as well when decorating --- django_valkey/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django_valkey/base.py b/django_valkey/base.py index fb40e08..de8e3d8 100644 --- a/django_valkey/base.py +++ b/django_valkey/base.py @@ -32,7 +32,7 @@ def decorate_all_methods(decorator): def decorate(cls): - for attr in vars(cls): + for attr in dir(cls): # dunders and `get` should not be decorated # get is handled by `_get` if attr.startswith("__") or attr in {"get", "get_or_set"}: From 2b20ed775fc4b31ef3e60ecdafd77bc1348697ae Mon Sep 17 00:00:00 2001 From: amirreza Date: Sat, 1 Nov 2025 20:34:53 +0330 Subject: [PATCH 2/7] remove `omit_exception` decorater from `BackendCommands` and `AsyncBackendCommands` --- django_valkey/base.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/django_valkey/base.py b/django_valkey/base.py index de8e3d8..c675279 100644 --- a/django_valkey/base.py +++ b/django_valkey/base.py @@ -199,7 +199,6 @@ def make_pattern(self, *args, **kwargs) -> bool: return self.client.make_pattern(*args, **kwargs) -@decorate_all_methods(omit_exception) class BackendCommands: def __contains__(self, item): return self.has_key(item) @@ -384,7 +383,6 @@ def hexists(self: BaseValkeyCache, *args, **kwargs) -> bool: return self.client.hexists(*args, **kwargs) -@decorate_all_methods(omit_exception) class AsyncBackendCommands: def __getattr__(self, item): if item.startswith("a"): From ecf4b95287a46e165ffa770342f59dda62f351c9 Mon Sep 17 00:00:00 2001 From: amirreza Date: Sat, 1 Nov 2025 20:35:33 +0330 Subject: [PATCH 3/7] add a decorated version of `BackendCommands` and `AsyncBackendCommands` --- django_valkey/async_cache/cache.py | 14 ++++++++++++-- django_valkey/cache.py | 14 ++++++++++++-- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/django_valkey/async_cache/cache.py b/django_valkey/async_cache/cache.py index e12a97c..2530594 100644 --- a/django_valkey/async_cache/cache.py +++ b/django_valkey/async_cache/cache.py @@ -1,11 +1,21 @@ from valkey.asyncio.client import Valkey as AValkey -from django_valkey.base import BaseValkeyCache, AsyncBackendCommands +from django_valkey.base import ( + BaseValkeyCache, + AsyncBackendCommands, + decorate_all_methods, + omit_exception, +) from django_valkey.async_cache.client.default import AsyncDefaultClient +@decorate_all_methods(omit_exception) +class DecoratedAsyncBackendCommands(AsyncBackendCommands): + pass + + class AsyncValkeyCache( - BaseValkeyCache[AsyncDefaultClient, AValkey], AsyncBackendCommands + BaseValkeyCache[AsyncDefaultClient, AValkey], DecoratedAsyncBackendCommands ): DEFAULT_CLIENT_CLASS = "django_valkey.async_cache.client.default.AsyncDefaultClient" is_async = True diff --git a/django_valkey/cache.py b/django_valkey/cache.py index 2bd26ef..e7c9aa7 100644 --- a/django_valkey/cache.py +++ b/django_valkey/cache.py @@ -1,8 +1,18 @@ from valkey import Valkey -from django_valkey.base import BaseValkeyCache, BackendCommands +from django_valkey.base import ( + BaseValkeyCache, + BackendCommands, + decorate_all_methods, + omit_exception, +) from django_valkey.client import DefaultClient -class ValkeyCache(BaseValkeyCache[DefaultClient, Valkey], BackendCommands): +@decorate_all_methods(omit_exception) +class DecoratedBackendCommands(BackendCommands): + pass + + +class ValkeyCache(BaseValkeyCache[DefaultClient, Valkey], DecoratedBackendCommands): DEFAULT_CLIENT_CLASS = "django_valkey.client.DefaultClient" From 9bf72ed4c68c1dd5423e0500fec19325d1aa9b99 Mon Sep 17 00:00:00 2001 From: amirreza Date: Sat, 1 Nov 2025 20:35:56 +0330 Subject: [PATCH 4/7] remove the undecorating loop from cluster --- django_valkey/base.py | 1 - django_valkey/cluster_cache/cache.py | 5 ----- 2 files changed, 6 deletions(-) diff --git a/django_valkey/base.py b/django_valkey/base.py index c675279..e21b92e 100644 --- a/django_valkey/base.py +++ b/django_valkey/base.py @@ -121,7 +121,6 @@ async def _async_generator_decorator(self, *args, **kwargs): else _generator_decorator ) - wrapper.original = method return wrapper diff --git a/django_valkey/cluster_cache/cache.py b/django_valkey/cluster_cache/cache.py index 642ce52..e4a152a 100644 --- a/django_valkey/cluster_cache/cache.py +++ b/django_valkey/cluster_cache/cache.py @@ -36,8 +36,3 @@ class ClusterValkeyCache( BackendCommands, ): DEFAULT_CLIENT_CLASS = "django_valkey.cluster_cache.client.DefaultClusterClient" - - -for name, value in vars(BackendCommands).items(): - if original := getattr(value, "original", None): - setattr(ClusterValkeyCache, name, original) From 30d085a8d755588a415cc8de2fe0653281cab394 Mon Sep 17 00:00:00 2001 From: amirreza Date: Tue, 4 Nov 2025 17:47:44 +0330 Subject: [PATCH 5/7] update changelog --- CHANGES.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index a9d6626..26cc6fa 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,11 @@ +### Breaking changes +- `BackendCommands` and `AsyncBackendCommands` are no longer decorated with `omit_exception` +- added `DecoratedBackendCommands` and `DecoratedAsyncBackendCommands` as commands decorated with `omit_exception` +- `AsyncValkeyCache` and `ValkeyCache` no longer inherit from `BackendCommands` and `AsyncBackendCommands`, they inherit from `DecoratedBackendCommands` and `DecoratedAsyncBackendCommands` instead + +### improvement +- removed the undecorator loop from cluster client + Version 0.3.2 ------------- From ddeb2d12fe0ffe218c9da1ebd6ecf5d23619c5f8 Mon Sep 17 00:00:00 2001 From: amirreza Date: Mon, 10 Nov 2025 18:48:00 +0330 Subject: [PATCH 6/7] separate sync and async `omit_exception` decorator for simplicity --- django_valkey/async_cache/cache.py | 4 +-- django_valkey/base.py | 47 +++++++++++++++++++++--------- 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/django_valkey/async_cache/cache.py b/django_valkey/async_cache/cache.py index 2530594..6f90297 100644 --- a/django_valkey/async_cache/cache.py +++ b/django_valkey/async_cache/cache.py @@ -4,12 +4,12 @@ BaseValkeyCache, AsyncBackendCommands, decorate_all_methods, - omit_exception, + omit_exception_async, ) from django_valkey.async_cache.client.default import AsyncDefaultClient -@decorate_all_methods(omit_exception) +@decorate_all_methods(omit_exception_async) class DecoratedAsyncBackendCommands(AsyncBackendCommands): pass diff --git a/django_valkey/base.py b/django_valkey/base.py index e21b92e..d2be327 100644 --- a/django_valkey/base.py +++ b/django_valkey/base.py @@ -4,7 +4,6 @@ import inspect import logging from collections.abc import AsyncGenerator, Callable, Iterator -from inspect import iscoroutinefunction from typing import ( Any, TypeVar, @@ -91,6 +90,34 @@ def _generator_decorator(self, *args, **kwargs): except ConnectionInterrupted as e: yield __handle_error(self, e) + # sync generators (iter_keys, sscan_iter) are only generator on client class + # in the backend they are just a function (that returns a generator) + # so inspect.isgeneratorfunction does not work + if not gen: + wrapper = _decorator + + # if method is a generator or async generator, it should be iterated over by this decorator + # generators don't error by simply being called, they need to be iterated over. + else: + wrapper = _generator_decorator + + return wrapper + + +def omit_exception_async( + method: Callable | None = None, return_value: Any | None = None, gen=False +): + if method is None: + return functools.partial(omit_exception, return_value=return_value) + + def __handle_error(self, e) -> Any | None: + if getattr(self, "_ignore_exceptions", None): + if getattr(self, "_log_ignored_exceptions", None): + self.logger.exception("Exception ignored") + + return return_value + raise e.__cause__ + @functools.wraps(method) async def _async_decorator(self, *args, **kwargs): try: @@ -106,20 +133,12 @@ async def _async_generator_decorator(self, *args, **kwargs): except ConnectionInterrupted as e: yield __handle_error(self, e) - # sync generators (iter_keys, sscan_iter) are only generator on client class - # in the backend they are just a function (that returns a generator) - # so inspect.isgeneratorfunction does not work - if not inspect.isasyncgenfunction(method) and not gen: - wrapper = _async_decorator if iscoroutinefunction(method) else _decorator - - # if method is a generator or async generator, it should be iterated over by this decorator - # generators don't error by simply being called, they need to be iterated over. + if inspect.isasyncgenfunction(method): + # if method is a generator or async generator, it should be iterated over by this decorator + # generators don't error by simply being called, they need to be iterated over. + wrapper = _async_generator_decorator else: - wrapper = ( - _async_generator_decorator - if inspect.isasyncgenfunction(method) - else _generator_decorator - ) + wrapper = _async_decorator return wrapper From 7c3615695572efb3b266d6db432c6a25e4af5a6a Mon Sep 17 00:00:00 2001 From: amirreza Date: Mon, 10 Nov 2025 18:55:36 +0330 Subject: [PATCH 7/7] update changelog --- CHANGES.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 26cc6fa..e39d16f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,9 @@ ### Breaking changes -- `BackendCommands` and `AsyncBackendCommands` are no longer decorated with `omit_exception` -- added `DecoratedBackendCommands` and `DecoratedAsyncBackendCommands` as commands decorated with `omit_exception` -- `AsyncValkeyCache` and `ValkeyCache` no longer inherit from `BackendCommands` and `AsyncBackendCommands`, they inherit from `DecoratedBackendCommands` and `DecoratedAsyncBackendCommands` instead +- `BackendCommands` and `AsyncBackendCommands` are no longer decorated with `omit_exception`. +- added `omit_exception_async` to decorate async operations, instead of using `omit_exception` for both sync and async. +- `omit_exception` no longer supports async functions and generators. +- added `DecoratedBackendCommands` and `DecoratedAsyncBackendCommands` as commands decorated with `omit_exception` and `omit_exception_async`. +- `AsyncValkeyCache` and `ValkeyCache` no longer inherit from `BackendCommands` and `AsyncBackendCommands`, they inherit from `DecoratedBackendCommands` and `DecoratedAsyncBackendCommands` instead. ### improvement - removed the undecorator loop from cluster client