Skip to main content

aiodrf-async-cache

Native async Redis and Valkey backends for Django's cache framework, built on the async clients of redis-py and valkey-py. It requires Python 3.12+ and Django 5.2+, and depends on neither Django REST framework nor django-aiodrf.

pip install "aiodrf-async-cache[redis]"    # redis-py
pip install "aiodrf-async-cache[valkey]"   # valkey-py

Usage

# settings.py
CACHES = {
    "default": {
        "BACKEND": "aiodrf_async_cache.redis.AsyncRedisCache",
        "LOCATION": "redis://localhost:6379/1",
        "TIMEOUT": 300,
        "KEY_PREFIX": "myapp",
        "VERSION": 1,
    },
}

For Valkey, use aiodrf_async_cache.valkey.AsyncValkeyCache with a valkey:// URL; it does not need django-valkey.

from django.core.cache import caches


async def cached_total():
    cache = caches["default"]
    await cache.aset("total", 42)
    return await cache.aget("total")

The backends subclass Django's BaseCache and implement its asynchronous API: aadd, aget, aset, atouch, adelete, aget_many, aset_many, adelete_many, ahas_key, aget_or_set, aincr, adecr, aincr_version, adecr_version, aclear and aclose, with Django's parameter names, defaults, and key, version and timeout rules. A missing value differs from a cached None, and aset_many returns the keys it could not set. Without a timeout the entry uses TIMEOUT; None keeps it, and zero or a negative timeout expires it at once. KEY_FUNCTION and a subclass's key validation apply as in Django.

Configuration and backend lookup stay in Django, as its cache design expects: there is no service registry and no patch. The default codec is Django's RedisSerializer, so integers stay usable by the server's atomic increments. Codec work for a batch runs in one worker call, and callbacks keep their order.

The backends are async only. Django's synchronous cache middleware, template fragment caching and other synchronous callers need a separate synchronous cache alias. Django 6.1's cache middleware is not asynchronous; the middleware in aiodrf_async_cache.middleware applies Django's page-cache policy with these backends.

Connection ownership

A backend's client belongs to the first event loop that uses it: close it on that loop with await cache.aclose() once its users are done, and do not share a backend between event loops. cache_lifespan() builds a backend of its own from a Django cache alias and closes it on exit, for resources that a worker owns:

from contextlib import asynccontextmanager

from aiodrf_async_cache.lifespan import cache_lifespan


@asynccontextmanager
async def resources():
    async with cache_lifespan("default") as cache:
        yield cache

With cache_lifespan("default", ping=True) the backend sends PING before it is yielded, so a server that cannot reach Redis fails at startup with the driver's connection error, not on the first request. The default, False, opens nothing until the first command.

The context manager works with any lifespan owner. AsyncCacheMiddleware reads the cache from the lifespan of aiodrf-asgi-lifespan (the lifespan extra) by default: set DJANGO_LIFESPAN = "project.lifecycle.resources" and serve its ASGI application. When the lifespan holds several clients, subclass the middleware and return the cache from get_cache(request); such a subclass does not need the lifespan package.

Options and limits

OPTIONS accepts:

  • callback_mode: "thread" (the default) runs synchronous callbacks in a worker thread; "inline" runs them on the event loop, for callbacks that never block.
  • serializer: a dotted path, a class or an object with dumps and loads.
  • max_connections (20), async_pool_class and async_pool_kwargs.

Other options go to the driver's async client. Async callbacks are awaited, and so is an awaitable that a synchronous callback returns. Decoded text responses (decode_responses) are refused: the codecs need bytes.

topology="sentinel" needs sentinels=[(host, port), ...] and takes the service name as LOCATION; sentinel_kwargs configures the discovery connections. topology="cluster" takes a seed URL as LOCATION and sends batches as separate commands per key, so their keys may be in different slots; such batches are not atomic. aclear() empties the whole selected database, or every primary in a cluster, whatever the KEY_PREFIX: see Security.

aincr and adecr need a codec that stores integers as Redis integers: Django's RedisSerializer, or a codec whose supports_integer_operations attribute is true.

Codecs

The default codec, Django's RedisSerializer, uses pickle: only connect to a cache that only trusted code can write to.

aiodrf_async_cache.codecs.MsgspecCodec (the msgspec extra) stores MessagePack, optionally decoded into a msgspec type:

from aiodrf_async_cache.codecs import MsgspecCodec

CACHES["default"]["OPTIONS"] = {"serializer": MsgspecCodec()}

It stores integers as Redis integers, so aincr and adecr work with it. Its values are not readable by Django's RedisCache, and it does not suit the page-cache middleware, which caches response objects.

CompressedCodec wraps another codec and compresses its values of at least min_size bytes:

from django.core.cache.backends.redis import RedisSerializer

from aiodrf_async_cache.codecs import CompressedCodec

CACHES["default"]["OPTIONS"] = {
    "serializer": CompressedCodec(RedisSerializer(), compressor="zlib", min_size=1024)
}
  • A compressed value starts with the byte 0xC1, which MessagePack never uses and pickle and Redis integers never start with, and a format byte (1 for zlib, 2 for zstd). Smaller values are stored as the inner codec wrote them, so values written before compression was enabled stay readable.
  • Integers are never compressed, so aincr works when the inner codec supports it.
  • compressor="zstd" needs the standard library's compression.zstd (CPython 3.14 or later); elsewhere it raises ImproperlyConfigured.
  • Django's RedisCache cannot read compressed values: do not enable it on entries a synchronous alias reads.

Single-flight aget_or_set

When a popular key expires, every concurrent request computes it again. aiodrf_async_cache.singleflight.aget_or_set has the signature of Django's aget_or_set and lets concurrent misses for the same key share one computation:

from aiodrf_async_cache.singleflight import aget_or_set


async def load_user():
    return await User.objects.aget(pk=42)


user = await aget_or_set(cache, "user:42", load_user, timeout=300)
  • default is a value or a callable; what a coroutine function returns is awaited.
  • Calls share a computation when they use the same cache object and the same final key (prefix, version and key). Callers that share one receive the same object.
  • A cancelled caller does not cancel the computation, which still stores its value. If the computation raises, every waiting caller gets the exception, nothing is cached, and the next call computes again.
  • Deduplication is per event loop, so each worker process computes a missing value once at a time. It works with any cache that implements Django's async API.

The django-valkey extra provides aiodrf_async_cache.django_valkey.LifespanConnectionFactory, which gives django-valkey's own backend pools owned by one lifespan. It is independent of the native backends.

Performance

The network I/O is native, but synchronous codec calls run in a worker thread by default, and that hop costs more than the codec on small values. Performance explains when callback_mode="inline" is safe. tools/benchmark.py measures both modes with both codecs.

Further documentation

Contributing

See CONTRIBUTING.md.

Metadata

Release files for aiodrf-async-cache 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for aiodrf-async-cache 0.2.0
File Size Uploaded Provenance Actions
aiodrf_async_cache-0.2.0.tar.gz 53.0 kB With provenance Details for aiodrf_async_cache-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for aiodrf-async-cache 0.2.0
File Interpreter ABI Platform Actions
aiodrf_async_cache-0.2.0-py3-none-any.whl Python 3 none any Details for aiodrf_async_cache-0.2.0-py3-none-any.whl

Total release size: 74.1 kB

Release files / aiodrf_async_cache-0.2.0.tar.gz

Download URL aiodrf_async_cache-0.2.0.tar.gz
Size 53.0 kB
Tags Source
SHA-256 checksum
How to use checksums
e0bb40b3338a7bb2a64b43c099743426608222290e9a8ca9ec0d173b715049e6
BLAKE2b-256 checksum
How to use checksums
65a38e84cf1688ea838bae6dd53a93e3d49a08710e221f518f8d11d81766c96e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 3, 2026.

Transparency log

Release files / aiodrf_async_cache-0.2.0-py3-none-any.whl

Download URL aiodrf_async_cache-0.2.0-py3-none-any.whl
Size 21.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ef3b6f07d1b2379c8aa27e043c252810367f040a5efc25339e3676e6199d5c34
BLAKE2b-256 checksum
How to use checksums
a6e3d01f27ad65ee2532b0ca9f02f0f0f2d7f6bb7dd30810bb66484fd9e271b7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 3, 2026.

Transparency log

Security for aiodrf-async-cache 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for aiodrf_async_cache-0.2.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 3, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page