Source code for action0.django_acache.options
"""
The configuration of the async connection pools.
Django's :py:class:`~django.core.cache.backends.redis.RedisCacheClient` turns the cache's
``OPTIONS`` into keyword arguments for its connection pools. Nearly all of them (``db``,
``password``, ``socket_timeout``, ...) mean the same to :py:mod:`redis.asyncio`, so the async
pools start out with the very same ones. A few name redis-py classes that come in a sync and an
async variant, though: ``pool_class``, ``parser_class``, ``connection_class``, ``retry``. The
cache setting ``ASYNC_OPTIONS`` overrides keys for the async pools only, and a sync class or
object that would still reach them is an error (see :py:mod:`~action0.django_acache.validation`).
"""
from collections.abc import Mapping
from typing import Any
from typing import TypeAlias
import redis.asyncio
from django.core.exceptions import ImproperlyConfigured
from django.utils.module_loading import import_string
from redis.asyncio.connection import DefaultParser
from .validation import check_async_options
AsyncPoolClass: TypeAlias = type[redis.asyncio.ConnectionPool]
#: ``OPTIONS`` keys that must stay the same for the sync and the async side: values written by
#: one side have to be readable by the other
SHARED_ONLY = frozenset({"serializer"})
[docs]
def async_pool_config(
pool_options: Mapping[str, Any], overrides: Mapping[str, Any]
) -> tuple[AsyncPoolClass, dict[str, Any]]:
"""
Derive the async pool class and pool options from the sync ones.
``pool_class`` and ``parser_class`` default to their :py:mod:`redis.asyncio` variants —
never to the sync ones configured in ``OPTIONS`` — and, like in ``OPTIONS``, both may be
given as dotted import paths.
>>> from redis.connection import DefaultParser as SyncParser
>>> pool_class, options = async_pool_config(
... {"db": 1, "parser_class": SyncParser},
... {"pool_class": "redis.asyncio.BlockingConnectionPool"},
... )
>>> pool_class.__name__, options["db"], options["parser_class"] is DefaultParser
('BlockingConnectionPool', 1, True)
:param pool_options: the keyword arguments Django built for its sync pools
:param overrides: the cache's ``ASYNC_OPTIONS``
:return: the pool class and the keyword arguments for its ``from_url()``
:raises ImproperlyConfigured: if ``overrides`` contains a key that must be shared, or if a
sync redis-py class or object would reach the async pools
"""
if shared := sorted(SHARED_ONLY & overrides.keys()):
raise ImproperlyConfigured(
f"ASYNC_OPTIONS cannot override {', '.join(shared)}: "
"the sync and the async side must read each other's values"
)
options = {**pool_options, "parser_class": DefaultParser, **overrides}
pool_class = _load(options.pop("pool_class", redis.asyncio.ConnectionPool))
options["parser_class"] = _load(options["parser_class"])
check_async_options({**options, "pool_class": pool_class}, overrides)
return pool_class, options
def _load(value: Any) -> Any:
"""Import ``value`` if it is a dotted path, like Django does for ``OPTIONS``."""
return import_string(value) if isinstance(value, str) else value