API Reference¶
Cache Methods¶
Standard Django Cache Methods¶
All standard Django cache methods are supported:
| Method | Description |
|---|---|
get(key, default=None) |
Get a value |
set(key, value, timeout=DEFAULT) |
Set a value |
add(key, value, timeout=DEFAULT) |
Set only if key doesn't exist |
delete(key) |
Delete a key |
touch(key, timeout=DEFAULT) |
Update timeout on a key |
get_many(keys) |
Get multiple values |
set_many(data, timeout=DEFAULT) |
Set multiple values |
delete_many(keys) |
Delete multiple keys |
get_or_set(key, default, timeout=DEFAULT) |
Get value or set default |
clear() |
Clear the cache |
has_key(key) |
Check if key exists |
incr(key, delta=1) |
Increment a value |
decr(key, delta=1) |
Decrement a value |
incr_version(key, delta=1) |
Increment key version |
decr_version(key, delta=1) |
Decrement key version |
close() |
Close connections |
Extended Methods¶
django-cachex adds these extended methods:
| Method | Description |
|---|---|
ttl(key) |
Get TTL in seconds (None = no expiry, -2 = not found) |
pttl(key) |
Get TTL in milliseconds (None = no expiry, -2 = not found) |
expire(key, timeout) |
Set expiration in seconds |
pexpire(key, timeout) |
Set expiration in milliseconds |
expireat(key, when) |
Set expiration at datetime |
pexpireat(key, when) |
Set expiration at datetime (ms precision) |
expiretime(key) |
Absolute Unix timestamp (seconds) when the key expires |
persist(key) |
Remove expiration |
type(key) |
Get the data type of a key |
lock(key, ...) |
Get a distributed lock |
keys(pattern) |
Get keys matching pattern |
iter_keys(pattern) |
Iterate keys matching pattern |
scan(cursor, pattern, count) |
Single SCAN iteration |
delete_pattern(pattern) |
Delete keys matching pattern |
rename(src, dst) |
Rename a key |
renamenx(src, dst) |
Rename key only if dest doesn't exist |
Hash Methods¶
Hash operations for field-value data structures:
| Method | Description |
|---|---|
hset(key, field=None, value=None, mapping=None, items=None) |
Set hash field(s); pass field/value, a mapping dict, or a flat items list |
hdel(key, *fields) |
Delete hash field(s) |
hexists(key, field) |
Check if hash field exists |
hget(key, field) |
Get a hash field value |
hgetall(key) |
Get all fields and values in a hash |
hkeys(key) |
Get all field names in a hash |
hincrby(key, field, amount=1) |
Increment hash field by integer |
hincrbyfloat(key, field, amount=1.0) |
Increment hash field by float |
hlen(key) |
Get number of fields in hash |
hmget(key, *fields) |
Get multiple hash field values |
hsetnx(key, field, value) |
Set hash field only if it doesn't exist |
hvals(key) |
Get all values in a hash |
Set Methods¶
Set operations for unordered collections of unique elements:
| Method | Description |
|---|---|
sadd(key, *members) |
Add member(s) to set |
srem(key, *members) |
Remove member(s) from set |
smembers(key) |
Get all members of set |
sismember(key, member) |
Check if member exists in set |
smismember(key, *members) |
Check if multiple members exist |
scard(key) |
Get number of members |
spop(key, count=None) |
Remove and return random member(s) |
srandmember(key, count=None) |
Get random member(s) without removing |
smove(src, dst, member) |
Move member between sets |
sdiff(keys) |
Get difference of sets |
sdiffstore(dest, keys) |
Store difference of sets |
sinter(keys) |
Get intersection of sets |
sinterstore(dest, keys) |
Store intersection of sets |
sunion(keys) |
Get union of sets |
sunionstore(dest, keys) |
Store union of sets |
sscan(key, cursor=0, ...) |
Incrementally iterate set members |
sscan_iter(key, ...) |
Iterate over set members using SSCAN |
keys on the multi-key set operations takes a single key or a sequence of them, not varargs. sdiff(["a", "b"]), not sdiff("a", "b"): the second positional argument is version.
Sorted Set Methods¶
Sorted set operations for scored, ordered collections:
| Method | Description |
|---|---|
zadd(key, mapping, *, nx, xx, ch, gt, lt) |
Add member(s) with scores |
zcard(key) |
Get number of members |
zcount(key, min_score, max_score) |
Count members with scores in range |
zincrby(key, amount, member) |
Increment member's score |
zrange(key, start, end, ...) |
Get members by index range |
zrevrange(key, start, end, ...) |
Get members by index range (descending) |
zrangebyscore(key, min_score, max_score, ...) |
Get members by score range |
zrevrangebyscore(key, max_score, min_score, ...) |
Get members by score range (descending) |
zrank(key, member) |
Get member's rank (ascending) |
zrevrank(key, member) |
Get member's rank (descending) |
zrem(key, *members) |
Remove member(s) |
zremrangebyrank(key, start, end) |
Remove members by rank range |
zremrangebyscore(key, min_score, max_score) |
Remove members by score range |
zscore(key, member) |
Get member's score |
zmscore(key, *members) |
Get multiple members' scores |
zpopmin(key, count=1) |
Remove and return members with lowest scores |
zpopmax(key, count=1) |
Remove and return members with highest scores |
List Methods¶
List operations for ordered, indexable collections:
| Method | Description |
|---|---|
llen(key) |
Get list length |
lpush(key, *values) |
Prepend value(s) to list |
rpush(key, *values) |
Append value(s) to list |
lpop(key) |
Remove and return first element |
rpop(key) |
Remove and return last element |
lindex(key, index) |
Get element by index |
lrange(key, start, end) |
Get elements in range |
lset(key, index, value) |
Set element at index |
ltrim(key, start, end) |
Trim list to range |
lrem(key, count, value) |
Remove elements equal to value |
lpos(key, value, ...) |
Find element position in list |
linsert(key, where, pivot, value) |
Insert value before or after pivot |
lmove(src, dst, wherefrom, whereto) |
Atomically move element between lists |
blpop(keys, timeout=0) |
Blocking pop from head of list |
brpop(keys, timeout=0) |
Blocking pop from tail of list |
blmove(src, dst, timeout, ...) |
Blocking move between lists |
blpop and brpop take keys the same way: one key or a sequence, followed by timeout.
Stream Methods¶
Append-only log structure with consumer groups:
| Method | Description |
|---|---|
xadd(key, fields, entry_id="*", maxlen=None, ...) |
Append an entry, returning its ID |
xlen(key) |
Number of entries in the stream |
xrange(key, start="-", end="+", count=None) |
Range of entries (forward) |
xrevrange(key, end="+", start="-", count=None) |
Range of entries (reverse) |
xread(streams, count=None, block=None) |
Read new entries from one or more streams |
xtrim(key, maxlen=None, approximate=True, minid=None, ...) |
Cap stream length |
xdel(key, *entry_ids) |
Delete entries by ID |
xinfo_stream(key, full=False) |
Stream metadata |
xinfo_groups(key) |
Consumer group metadata |
xinfo_consumers(key, group) |
Per-consumer metadata for a group |
xgroup_create(key, group, entry_id="$", mkstream=False) |
Create a consumer group |
xgroup_destroy(key, group) |
Drop a consumer group |
xgroup_setid(key, group, entry_id) |
Re-anchor a consumer group's read position |
xgroup_delconsumer(key, group, consumer) |
Drop a consumer from a group |
xreadgroup(group, consumer, streams, count=None, block=None) |
Read entries as a group consumer |
xack(key, group, *entry_ids) |
Acknowledge processed entries |
xpending(key, group, ...) |
Inspect pending (unacked) entries |
xclaim(key, group, consumer, min_idle_time, entry_ids, ...) |
Claim pending entries |
xautoclaim(key, group, consumer, min_idle_time, ...) |
Auto-claim entries idle longer than threshold |
All methods have an a* async counterpart (e.g. axadd, axreadgroup).
Lua Script Methods¶
Execute Lua scripts with optional key prefixing and value encoding/decoding:
| Method | Description |
|---|---|
eval_script(script, *, keys, args, ...) |
Execute a Lua script |
aeval_script(script, *, keys, args, ...) |
Execute a Lua script (async) |
eval_script / aeval_script¶
result = cache.eval_script(
script, # Lua script source code
keys=(), # KEYS to pass to script
args=(), # ARGV to pass to script
pre_hook=None, # Pre-processing hook: (helpers, keys, args) -> (keys, args)
post_hook=None, # Post-processing hook: (helpers, result) -> result
version=None, # Key version for prefixing
)
Pre-built Hooks¶
| Hook | Description |
|---|---|
keys_only_pre |
Prefix keys, leave args unchanged |
full_encode_pre |
Prefix keys AND encode all args |
decode_single_post |
Decode a single returned value |
decode_list_post |
Decode a list of returned values |
Pass post_hook=None (the default) when no decoding is needed.
ScriptHelpers¶
The helpers object passed to pre/post hooks:
| Attribute/Method | Description |
|---|---|
make_key(key, version) |
Apply cache key prefix |
make_keys(keys) |
Prefix multiple keys |
encode(value) |
Encode a value (serialize + compress) |
encode_values(values) |
Encode multiple values |
decode(value) |
Decode a value |
decode_values(values) |
Decode multiple values |
version |
Current key version |
Set Method Options¶
| Parameter | Description |
|---|---|
timeout |
Expiration in seconds (None = never, 0 = immediate) |
nx |
Only set if key doesn't exist (SETNX) |
xx |
Only set if key exists |
get |
Return the previous value (atomic get-and-set) |
Async Methods¶
Standard Django cache methods have async versions on the cache object:
aadd,aget,aset,adelete,atouch,aget_many,aset_many,adelete_manyahas_key,aincr,adecr,aget_or_set,aclear,acloseaincr_version,adecr_versionaeval_script
Extended methods (data structures, TTL, patterns) have async versions directly on the cache. Both forms apply key prefixing and the serializer/compressor pipeline:
For raw access that skips prefixing/serialization, use cache.adapter (e.g. await cache.adapter.aget(prefixed_key)).
attl,apttl,aexpire,apexpire,aexpireat,apexpireat,apersistakeys,aiter_keys,adelete_patternahset,ahdel,ahexists,ahget,ahgetall,ahincrby,ahincrbyfloat,ahkeys,ahlen,ahmget,ahsetnx,ahvalsasadd,asrem,asmembers,asismember,asmismember,ascard,aspop,asrandmember,asmove,asdiff,asdiffstore,asinter,asinterstore,asunion,asunionstoreazadd,azcard,azcount,azincrby,azrange,azrevrange,azrangebyscore,azrevrangebyscore,azrank,azrevrank,azrem,azremrangebyrank,azremrangebyscore,azscore,azmscore,azpopmin,azpopmaxallen,alpush,arpush,alpop,arpop,alindex,alrange,alset,altrim,alrem,alpos,almove,alinsert,ablpop,abrpop,ablmoveasscan,asscan_iter
Raw Client Access¶
| Parameter | Description |
|---|---|
write |
Get write connection for primary (default: False) |
Returns the underlying client object. The concrete type depends on which adapter is configured:
| Backend | get_client() returns |
|---|---|
ValkeyCache (valkey-py) |
valkey.Valkey |
RedisCache (redis-py) |
redis.Redis |
RedisRsCache (redis-rs) |
django_cachex.adapters.RedisRsAdapter (the adapter is its own multiplexed client) |
ValkeyGlideCache (valkey-glide) |
glide_sync.GlideClient |
The four objects expose comparable command surfaces but are not
interchangeable types; code that pins to one adapter for type-narrowing
or vendor-specific calls won't work against the others. For
adapter-portable code use the cache API directly; reach for
get_client() only as an escape hatch, since it bypasses the configured
key prefix, version, serializer, and compressor.
The async equivalent is get_async_client(), which is async def and
returns the adapter's async client.
Lock Interface¶
| Parameter | Description |
|---|---|
key |
Lock name |
lease |
TTL of the held lock; the lock is auto-released after this many seconds (no auto-release if None) |
sleep |
Time between acquire attempts |
blocking |
Wait for lock if held |
timeout |
Max time acquire() will wait before giving up (no upper bound if None) |
acquire() accepts the same blocking / timeout arguments to override
the defaults set on the lock object:
Compatible with threading.Lock:
# Context manager
with cache.lock("mylock"):
do_work()
# Manual acquire/release
lock = cache.lock("mylock")
if lock.acquire():
try:
do_work()
finally:
lock.release()
Async lock¶
alock() is async def (parallel to apipeline()):
# Context manager
async with await cache.alock("mylock"):
await do_work()
# Manual acquire/release
lock = await cache.alock("mylock")
if await lock.acquire():
try:
await do_work()
finally:
await lock.release()
Cluster mode rejects lock() and alock() on every adapter: the release script runs via EVALSHA, which cluster routes to replicas, so release() would fail and the key would stay set until its lease expired. Both raise NotSupportedError. Use semaphore() instead, which colocates its keys under a {name} hash tag.
Semaphore Interface¶
Return a weighted semaphore for concurrency gating. Use as a context manager.
with cache.semaphore("image-convert", capacity=4):
# Up to 4 callers may hold this semaphore concurrently.
convert(...)
# Weighted: claim 100 of a 500 budget.
with cache.semaphore("memory-heavy", weight=100, capacity=500, lease=300):
convert_huge(...)
| Parameter | Description |
|---|---|
key |
Logical name of the semaphore. Callers with the same name share budget. |
capacity |
Total budget. The first caller establishes capacity. Subsequent callers passing a different value update it on every backend; the local backend additionally emits a RuntimeWarning (the RESP backend updates silently). |
weight |
How much of the capacity this caller claims (default 1, i.e. counting semaphore). |
version |
Optional cache version namespace. |
lease |
TTL of the held claim in seconds. Required for the RESP backend (auto-reclaim if the holder crashes); accepted but ignored on the local backend. |
timeout |
Max time acquire() will wait before raising SemaphoreTimeoutError. None blocks indefinitely. |
acquire() accepts blocking and timeout to override the defaults set on the semaphore object. release() returns the claim to the pool; extend(seconds) bumps the TTL on RESP backends for tasks that may legitimately exceed their original lease.
sem = cache.semaphore("mysem", capacity=4, lease=30)
if sem.acquire(timeout=5):
try:
do_work()
finally:
sem.release()
Async semaphore¶
asemaphore() is async def (parallel to alock()):
# Context manager
async with await cache.asemaphore("mysem", capacity=4, lease=30):
await do_work()
# Manual acquire/release
sem = await cache.asemaphore("mysem", capacity=4, lease=30)
if await sem.aacquire():
try:
await do_work()
finally:
await sem.arelease()
The awaitable methods are aacquire() and arelease(). acquire() and
release() are the sync pair and are still present on the same object, so
await sem.acquire() runs the sync acquire and then raises TypeError on the
returned bool, leaving the claim held until its lease expires.
Sync and async callers on the same cache instance share state for a given name.
Backends:
LocMemCacheuses an in-process FIFO deque. FIFO fairness is strict within the process;leaseis accepted but ignored.- RESP backends (
RedisCache,ValkeyCache,RedisRsCache,ValkeyGlideCache, ...) use Lua scripts. FIFO fairness is best-effort across processes (head-of-queue check plus jittered polling).
Cluster mode is supported on RESP backends: all keys for one semaphore name carry a {name} hash tag so they colocate on the same slot.
Pipelines¶
Batch multiple operations for efficiency. Queueing methods (set, hset, lpush, ...) stay synchronous in both wrappers; only execute() performs I/O.
Sync¶
with cache.pipeline() as pipe:
pipe.set("key1", "value1")
pipe.set("key2", "value2")
pipe.hset("hash", "field", "value")
results = pipe.execute()
Async¶
async with await cache.apipeline() as pipe:
pipe.set("key1", "value1")
pipe.hset("hash", "field", "value")
results = await pipe.execute()
apipeline() is async def so adapters whose async-client construction is itself awaitable (e.g. valkey-glide) can resolve the client before returning the wrapper. Queueing methods stay synchronous; only apipeline() and execute() need to be awaited.
Single-key commands are available on the pipeline. The multi-key helpers (set_many, get_many, delete_many), the read-modify-write helpers (get_or_set, add, touch, has_key), and the scanning helpers (keys, scan, delete_pattern, clear) are not: queue their underlying commands instead. Results are returned as a list in the same order as the commands.
Clearing keys¶
| Method | Description |
|---|---|
clear() / aclear() |
Remove only this cache's keys (KEY_PREFIX + VERSION). Implemented as delete_pattern("*"). |
flush_db() / aflush_db() |
FLUSHDB: remove all keys in the underlying Redis/Valkey database, regardless of prefix. |
clear() is safe when multiple apps share a Redis database. Use flush_db() only when you really want to flush the whole database and not the configured Django namespace.
Settings Reference¶
Cache OPTIONS¶
| Option | Description |
|---|---|
serializer |
Serializer class or list for fallback |
compressor |
Compressor class or list for fallback |
password |
Server password |
socket_connect_timeout |
Connection timeout |
socket_timeout |
Read/write timeout |
pool_class |
Custom connection pool class (sync) |
async_pool_class |
Custom connection pool class (async) |
parser_class |
Custom RESP parser class |
stampede_prevention |
True / False / dict (buffer, beta, delta); see StampedeConfig |
sentinels |
Sentinel server list (for Sentinel backends) |
sentinel_kwargs |
Sentinel configuration |
StampedeConfig¶
django_cachex.StampedeConfig(buffer=60, beta=1.0, delta=1.0), frozen
dataclass that tunes the TTL-based XFetch stampede-prevention algorithm.
Pass it to OPTIONS["stampede_prevention"] to apply globally, or to the
stampede_prevention= kwarg on get/set/get_many/set_many/add/
touch/get_or_set, and their a-prefixed async counterparts, for
per-call overrides. touch uses it to decide whether the refreshed TTL
gets the buffer added back.
| Field | Default | Description |
|---|---|---|
buffer |
60 |
Seconds added to TTL on writes; defines the early-recompute window. |
beta |
1.0 |
Multiplier on the recompute probability; higher = recompute earlier. |
delta |
1.0 |
Recompute-cost estimate (seconds); larger = recompute earlier. |
Exceptions¶
All importable from the package root (django_cachex). Every exception
subclasses CachexError, so one except CachexError handles any library
failure.
| Exception | Description |
|---|---|
CachexError |
Base class for every exception raised by django-cachex. |
WrongTypeError |
Operation applied to a key holding the wrong RESP type (subclass of TypeError). Mirrors Redis WRONGTYPE; raised consistently across LocMem, redis-py, valkey-py, valkey-glide, and the Rust adapter. |
CompressorError |
Compression or decompression failed. Triggers the configured compressor fallback chain. |
SerializerError |
Serialization or deserialization failed. Triggers the serializer fallback chain. |
NotSupportedError |
Operation is not supported by this backend (e.g. lpush on TieredCache). |
LockError |
A lock operation failed (couldn't acquire, releasing an unlocked lock, ...). |
LockNotOwnedError |
Releasing or extending a lock the caller no longer owns (expired or stolen). Subclass of LockError. |
SemaphoreError |
A semaphore operation failed (e.g. re-acquiring before release). |
SemaphoreTimeoutError |
timeout elapsed before the semaphore could be acquired. Subclass of SemaphoreError. |