Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
### Breaking changes
- `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

Version 0.3.2
-------------

Expand Down
14 changes: 12 additions & 2 deletions django_valkey/async_cache/cache.py
Original file line number Diff line number Diff line change
@@ -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_async,
)
from django_valkey.async_cache.client.default import AsyncDefaultClient


@decorate_all_methods(omit_exception_async)
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
52 changes: 34 additions & 18 deletions django_valkey/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import inspect
import logging
from collections.abc import AsyncGenerator, Callable, Iterator
from inspect import iscoroutinefunction
from typing import (
Any,
TypeVar,
Expand Down Expand Up @@ -32,7 +31,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"}:
Expand Down Expand Up @@ -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:
Expand All @@ -106,22 +133,13 @@ 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

wrapper.original = method
return wrapper


Expand Down Expand Up @@ -199,7 +217,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)
Expand Down Expand Up @@ -384,7 +401,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"):
Expand Down
14 changes: 12 additions & 2 deletions django_valkey/cache.py
Original file line number Diff line number Diff line change
@@ -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"
5 changes: 0 additions & 5 deletions django_valkey/cluster_cache/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)