Guide
API
django-vcache implements the full Django cache API with native async variants (aget, aset, etc.):
get, set, add, delete, touch, incr, decr, has_key, get_many, set_many, delete_many, get_or_set, clear
clear() flushes the whole database
Like Django's built-in RedisCache and django-redis, clear() issues
FLUSHDB — it empties the entire logical database, ignoring
KEY_PREFIX. If the same database also holds other data (for example a
django-vtasks task queue), that data is deleted too. Point co-tenants at
different database numbers if you need clear() to be safe.
Distributed locking
lock(key, timeout=None, sleep=0.1, blocking=True, blocking_timeout=None) / alock(...) — Distributed locking via Valkey. Not available in cluster mode.
from django_vcache import LockError
try:
with cache.lock("my-lock", timeout=10, blocking_timeout=5):
# exclusive access
...
except LockError:
... # someone else holds the lock
async with cache.alock("my-lock", timeout=10):
...
timeout— lock TTL in seconds (None= held until released).blocking=False— single acquisition attempt, no retry loop.blocking_timeout— how long to keep retrying:Nonewaits forever,0tries exactly once.- The context manager raises
LockErrorwhen the lock cannot be acquired — the critical section never runs unguarded. Callacquire()directly if you want aboolinstead:
lock = cache.lock("my-lock", blocking=False)
if lock.acquire():
try:
...
finally:
lock.release()
Release is owner-checked (a Lua compare-and-delete on a per-lock token), so a lock that expired and was re-acquired elsewhere cannot be released by the original holder.
Raw client access
get_raw_client() — Access the underlying Rust driver instance for operations not covered by the Django cache API (list operations, blmove/blmpop, scan_all, Lua scripting, pipelines). Reuses the existing connection.
client = cache.get_raw_client()
await client.lpush("queue", [b"job"])
item = await client.blmove("queue", "processing", "RIGHT", "LEFT", 1.0)
Driver awaitables integrate with asyncio: they work under asyncio.gather,
asyncio.wait_for, and asyncio.shield, and cancellation is real — cancelling
a blocking blmove/blmpop aborts the operation server-side (the dedicated
blocking connection is closed, so the server discards the blocked command).
Each in-flight blocking operation holds its own connection, so concurrent
blocking consumers never serialize behind each other's timeouts. See
VCACHE_MAX_IDLE_BLOCKING_CONNS
if you run many blocking consumers per process.
Atomic incr/decr
incr and decr use native Redis INCRBY/DECRBY commands. If the key does not exist, it is created with the delta value (Redis behavior). This is atomic and safe for concurrent counters.
Serialization
Values are serialized with msgpack (via ormsgpack) by default. Large values (>1KB by default) are compressed with zstd. Integer values are stored as raw strings to support native INCRBY.
Msgpack caveats compared to pickle:
- Dict keys must be strings — caching
{42: "x"}raisesTypeErroratset()time. - Tuples round-trip as lists.
Use SERIALIZER: "pickle" if you need to cache arbitrary Python objects,
with the usual pickle trust caveats.
Values the configured serializer cannot decode — foreign data, corrupt
payloads, or values written under a different SERIALIZER setting — are
treated as cache misses (get returns your default) rather than raising.
Changing SERIALIZER against a warm cache is therefore safe: old entries
just miss until they expire or are overwritten.
See Configuration for options.