Source code for alchemiq.cache.backend

"""Cache backend protocol and process-global registry."""

from __future__ import annotations

from typing import Any, Protocol, runtime_checkable

from alchemiq.exceptions import ConfigError


[docs] @runtime_checkable class CacheBackend(Protocol): """Structural protocol for cache backends. Implemented by :class:`.InMemoryCache` (core, no extra) and ``RedisCache`` (requires the ``[redis]`` extra, built automatically by :func:`.configure_cache` when a Redis ``url`` is supplied). Any object that exposes ``default_ttl``, ``namespace``, and the five async methods below satisfies this protocol (``isinstance`` check via ``@runtime_checkable``). .. seealso:: :func:`.configure_cache` - register a backend process-globally. """ default_ttl: int namespace: str
[docs] async def get(self, key: str) -> str | None: """Return the cached string, or ``None`` on miss or expiry.""" ...
[docs] async def set(self, key: str, value: str, *, ttl: int) -> None: """Store ``value`` under ``key`` with a TTL in seconds.""" ...
[docs] async def delete(self, *keys: str) -> None: """Delete one or more keys; no-ops on missing keys.""" ...
[docs] async def incr(self, key: str) -> int: """Atomically increment an integer counter and return the new value.""" ...
[docs] async def scan_delete(self, match: str) -> None: """Delete all keys matching a glob pattern.""" ...
_cache: CacheBackend | None = None
[docs] def configure_cache( url: str | None = None, *, backend: CacheBackend | None = None, default_ttl: int = 60, namespace: str = "aq", **redis_kw: Any, ) -> None: """Set the process-global cache backend. Pass an explicit ``backend`` instance, or a Redis connection ``url`` (requires the ``[redis]`` extra). Only one may be provided. Call :func:`.reset_cache` to clear the global backend (useful in tests). E.g.:: from alchemiq.cache import InMemoryCache, configure_cache, reset_cache # in-process cache (no dependencies): configure_cache(backend=InMemoryCache(default_ttl=120)) # Redis (requires [redis] extra): configure_cache(url="redis://localhost:6379/0") :param url: Redis connection URL; builds a :class:`.CacheBackend` backed by ``redis.asyncio``. Requires ``pip install 'alchemiq[redis]'``. :param backend: A :class:`.CacheBackend` instance to use directly. When given, ``default_ttl`` and ``namespace`` are ignored. :param default_ttl: Default TTL in seconds for Redis-built backends (default 60). :param namespace: Key prefix for Redis-built backends (default ``"aq"``). :raises ConfigError: if neither ``url`` nor ``backend`` is provided. :raises ImportError: if ``url`` is given but the ``[redis]`` extra is not installed. .. seealso:: :class:`.InMemoryCache` - zero-dependency backend for tests. """ global _cache if backend is not None: _cache = backend return if url is not None: try: from alchemiq.cache.redis import RedisCache # ty: ignore[unresolved-import] except ImportError as e: raise ImportError( "Redis caching requires the [redis] extra: pip install 'alchemiq[redis]'" ) from e _cache = RedisCache(url, default_ttl=default_ttl, namespace=namespace, **redis_kw) return raise ConfigError("configure_cache() requires either url= or backend=")
def get_cache() -> CacheBackend | None: """Return the active global cache backend, or ``None`` if not configured.""" return _cache
[docs] def reset_cache() -> None: """Clear the global cache backend. Intended for teardown and test isolation. Mirrors ``runtime.dispose()``. """ global _cache _cache = None