Skip to content

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: None waits forever, 0 tries exactly once.
  • The context manager raises LockError when the lock cannot be acquired — the critical section never runs unguarded. Call acquire() directly if you want a bool instead:
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"} raises TypeError at set() 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.