Changelog¶
0.4.0 (June 2026)¶
Breaking changes¶
- Python 3.14+ required. Dropped support for 3.12 and 3.13. The package now ships on cp314 and cp314t (free-threaded) wheels.
- Django 6.0+ required. Dropped support for Django 5.2.
LocMemCachedata structures use tagged subclasses. Lists, sets, hashes, and sorted sets are stored as dedicated subclasses (_List,_Set,_Hash,_ZSet) rather than plain Python types, and cross-type access raisesWrongTypeErrorinstead of silently coercing, matching real Valkey/RedisWRONGTYPEsemantics.LocMemCachebypasses pickle for tagged collections. Mutations happen in place; the prior copy-on-read/copy-on-write contract no longer holds. Code that relied on getting a detached snapshot fromcache.get()for these types now sees the live structure.StreamCachewire format changed. Stream entries now flow through the transport's serializer + compressor pipeline instead of raw pickle. Pods running the new code cannot read entries written by older pods on the same stream; coordinate the rollout (drain or rotatestream_key).hmsetremoved. Usehset(key, mapping=...)orhset(key, items=...)(flat key-value list, matching redis-py/valkey-py).django_cachex.unfoldremoved. The django-unfold theme variant of the admin is gone, along with the[unfold]extra andexamples/unfold/. Plaindjango_cachex.adminremains. Unfold support may return as a thin theme override once the core admin app stabilises.- Lock parameters renamed.
cache.lock(timeout=...)is nowcache.lock(lease=...)(TTL of the held lock);lock.acquire(blocking_timeout=...)is nowlock.acquire(timeout=...)(max wait). The oldblocking_timeoutkwarg raisesTypeError; the constructor's newtimeout=kwarg means "max wait" rather than "TTL". No deprecation shim. This aligns the lock API with the upcomingcache.semaphore(...)primitive. ZStdCompressorrenamed toZstdCompressor(django_cachex.compressors.zstd.ZstdCompressor). UpdateOPTIONS["compressor"]strings.LzmaCompressorconstructorpreset=renamed tolevel=for consistency with the other compressors. All compressors now acceptlevel=(mapped to the underlying library's native parameter).PickleSerializerno longer raisesImproperlyConfiguredforprotocol > pickle.HIGHEST_PROTOCOL. Pickle's ownValueErroris now surfaced at the firstdumpscall, wrapped asSerializerError(with the pickle exception as__cause__).CachexCompatremoved. The mixin class that emulated the cachex ext surface on top of an arbitraryBaseCacheis gone, along with the admin's "wrapped" support tier. Django'sBaseCacheand the stock backends (LocMemCache,RedisCache,DatabaseCache,FileBasedCache,MemcachedCache,DummyCache) deliberately don't expose key listing, so the wrap couldn't drive the admin's browse views meaningfully. Usedjango_cachex.cache.LocMemCache/DatabaseCache(drop-in replacements) for full admin support; non-cachex backends now show as "limited" (configuration only).- Cluster
LOCATIONwith a database number now raises on the redis-py and valkey-py cluster backends. Those two are built with the driver'sfrom_url(), which rejects a non-zerodbin the URL path or query (RedisClusterException/ValkeyClusterException). The old code read only host and port off the URL, soredis://host:6379/1connected to db 0 without complaint. Cluster has noSELECT, so the number was never honored; drop it fromLOCATION.ValkeyGlideClusterCacheandRedisRsClusterCachestill ignore it silently.
New features¶
- Rust I/O driver (experimental). Optional native driver built on PyO3 + tokio + redis-rs, shipped as a separate
django-cachex-redis-rspackage. Interfaces and behavior may change, and it has seen less production testing than the redis-py/valkey-py paths. Opt in via theredis-rsextra (pip install django-cachex[redis-rs]); without it, only the pure-Python backends are pulled in and theRedisRsCacheclasses raise a cleanImportErroron first use. SetBACKENDto one ofRedisRsCache,RedisRsClusterCache, orRedisRsSentinelCache. Sync and async share one tokio runtime; async dodges the threadpool round-trip. valkey-glideadapter (experimental). Optional Rust-cored client from the Valkey project. Interfaces and behavior may change, and it has seen less production testing than the redis-py/valkey-py paths. Opt in via thevalkey-glideextra. Standalone (ValkeyGlideCache) and cluster (ValkeyGlideClusterCache) topologies are exposed; Sentinel is not (valkey-glideitself does not ship a Sentinel client).WrongTypeErrorexception. Backends now translate RedisWRONGTYPEresponses into a singledjango_cachex.WrongTypeError(subclass ofTypeError) so user code can catch one exception across LocMem, redis-py, valkey-py, valkey-glide, and the Rust adapter.- Async ext methods on LocMem and Database. The full async data-structure surface (
alpush,ahset,azadd,attl,aexpire, ...) is now available onLocMemCache(direct sync calls; in-memory, so no I/O to offload) andDatabaseCache(viasync_to_async, the same path Django uses forBaseCache.aget). They no longer raiseNotSupportedErrorfrom async views. StreamCachebackend. Stream-synchronized in-memory cache: reads are local, writes broadcast over a Redis Stream, a daemon thread on each pod consumes the stream and applies remote changes. Read-heavy, write-light, eventually consistent.TieredCachebackend. Composes two existingCACHESentries as L1 (fast, e.g. LocMem) and L2 (durable, e.g. Redis), with TTL propagation and pull-through reads.- Cache-stampede prevention. TTL-based XFetch via
OPTIONS["stampede_prevention"](orstampede_prevention=per call). Configurable buffer/beta/delta. LocMemCacheandDatabaseCacheextensions. Drop-in replacements for the Django builtins, adding data-structure ops, TTL helpers, and admin support. Compound read-modify-write ops onLocMemCacheare serialized via a per-backendRLock(#62).orjsonandormsgpackserializer extras.- Free-threaded CPython (3.14t) support. A cp314t wheel is built;
_redis_rsworks with the GIL disabled. The Rust driver also runs on the free-threaded build. - PyPI wheels via cibuildwheel. Wheels for Linux x86_64, Linux aarch64, macOS arm64, and Windows amd64, on cp314 and cp314t.
- Async pool sharing. A single async connection pool is shared across per-task
Cacheinstances (#83), avoiding the thundering-herd reconnect on cold start. - Pipeline parity. Stream ops, CAS ops, missing key ops (
persist/pttl/expireat/etc.), context manager,zpopmin/zpopmaxdefaultcount=1aligned with the cache API. - Compressors gain a uniform
level=parameter (gzip, lz4, zstd join zlib/lzma in exposing it). Defaults match each library's own default. - Serializer/compressor wrappers consolidated. Subclasses now implement
_dumps/_loads(serializers) or_compress/_decompress(compressors); the base classes wrap the boilerplate (SerializerError/CompressorErrortranslation, int-passthrough on loads). - Weighted semaphores. New
cache.semaphore(name, capacity, *, weight=1, lease=..., timeout=...)andcache.asemaphore(...)for gating concurrent access by a budget (counting or weighted). Backed by an in-process FIFO deque onLocMemCacheand by Lua scripts on the RESP backends (redis-py, redis-rs, valkey-py, valkey-glide). Cluster mode is supported via{name}hash-tag colocation. Sync and async APIs share state per cache instance; lease-based crash reclaim on the RESP backend (no heartbeat). Seedocs/recipes.mdfor examples.
Performance¶
LocMemCachesorted sets are O(log N). Sorted-set operations now back the underlying dict with asortedcontainers.SortedListsidecar for O(log N) insertion, deletion, and rank queries; previous implementation was O(N log N) per write. Addssortedcontainers>=2.4as a runtime dependency.LocMemCacheskips pickle for tagged collections. Tagged subclasses are mutated in place; reads and writes no longer round-trip through pickle for list/set/hash/zset/stream types.
Fixes¶
LocMemCache.lpush/sadd/hset/hincrby/zadd/etc. no longer lose updates under concurrent threads (#62).delete_patternbatches deletes to bound peak memory on broad patterns.clear()is now prefix/version-scoped instead ofFLUSHDB. The old behavior is available asflush_db().- Compressor
compressanddecompressmethods catch all exceptions and re-raise asCompressorError. - Several cluster correctness fixes (script loading on replicas, set_many
timeout=0). - Fixed a crash when reading values small enough to have skipped compression (at or below the compressor's
min_length). - Admin cache/key changelists are compatible with Django 6.1.
- Semaphore waiters abandoned by crashed or cancelled callers are reaped instead of blocking the queue.
- valkey-glide: connection options reach the client instead of being reduced to host and port. The TLS scheme (
rediss/valkeys) oruse_tls/ssl, credentials from the URL orOPTIONS, the database index (standalone only),request_timeout, andclient_nameare all applied;zaddforwards thegt/ltflags, and pipelines support the stream commands. TieredCache.setforwardsnx/xxto L2, and an L2 that is a stock Django backend no longer raisesTypeError:nxfalls back toadd(),xx/getraiseNotSupportedError, and a plain set drops the flags.set(..., timeout=0)deletes the key across all backends, matching Django's cache contract.LocMemCachealiases sharing aLOCATIONshare one store, including the tagged collections and the semaphore budgets, matching Django's builtin behavior.- Admin: backend capability probes fail gracefully, and key URLs are quoted so keys with special characters open correctly.
- CI runs the test matrix against Django 6.1 in addition to 6.0.
- Dependabot automerge waits for every workflow run on the PR head to succeed before merging.
reverse_key()handles aKEY_PREFIXcontaining colons, sokeys(),iter_keys(),scan(), and the blocking list pops return user keys instead of raw internal ones.DatabaseCachecompound ops (rpush,sadd,zadd,hset, ...) that lose the insert race against a concurrent writer now merge with the committed row instead of overwriting it.LocMemCacheandDatabaseCachehincrby/hincrbyfloatreject non-numeric stored values with the same error as the server instead of truncating them.TieredCacherejectsKEY_PREFIXin the standard top-level slot as well as inOPTIONS; it was silently ignored before.- Sentinel: async connection pools are keyed by sentinel fleet, so two aliases sharing a service name no longer alias onto one pool.
- Semaphores: concurrent
acquire()on oneRespSemaphoreinstance can no longer double-claim and leak a slot until the lease expires. - Admin: editing a key preserves its TTL and persistence instead of resetting it to the default timeout. Covers every backend, including those that report no-expiry as
-1rather thanNone(StreamCache) and those withoutpexpire(StreamCache,TieredCache). StreamCacheenqueues each broadcast while still holding the local write lock, so a pod's stream entries carry the order its writes were applied and replaying consumers converge on the writer's final value instead of an older one.keys()is scoped to the cache's own prefix and version.- Pipelines discard their queued decoders when
execute()raises, so a reused pipeline no longer decodes the next batch against a stale, misaligned decoder list.AsyncPipelinerejects a syncwithat entry rather than after the block has run. - The redis-py and valkey-py cluster backends are built from the full server URL through the driver's
from_url(), so the TLS scheme, credentials, and query parameters survive; only the host and port were read before. The async Sentinel pool cache is also keyed on the sentinel fleet rather than the manager'sid(), so the per-task adapters asgiref creates share one pool instead of each opening its own. encode()passes through exactintvalues only.intsubclasses (IntEnum,IntFlag) now go through the serializer, so they come back as their own type instead of as plain ints.touch()/atouch()apply the stampede buffer to the TTL they write and accept a per-callstampede_prevention=. Touching a key under stampede prevention no longer strips the buffer and pushes every reader into a recompute.DatabaseCachekey scans escape SQLLIKEmetacharacters per database vendor, so aKEY_PREFIXor pattern containing%,_, or a backslash no longer matches unrelated rows.DatabaseCache.zadd/zincrbyreject a non-numeric score withValueErrorbefore writing, matching the server, instead of storing a value that breaks later range queries.MAX_ENTRIESculling covers the whole store:LocMemCachecounts its tagged collections alongside the pickled entries and evicts them, andDatabaseCachecompound ops (rpush,sadd,hset, ...) run the same cull check as a plainset()when they insert a new row.LocMemCachecollection edge cases:keys()scopes to the requested version and skips expired-but-not-yet-culled entries,incr()on a collection key raisesWrongTypeErrorinstead ofKeyError, andsadd/hset/zaddno longer leave an empty key behind when the call adds nothing (zaddwherenx/xxskip every member,sadd/hsetcalled with no members or fields).rpop(count=0)onLocMemCacheandDatabaseCache, andzpopmax(count=0)onLocMemCache, return an empty list instead of draining the whole collection.
0.3.0 (February 2026)¶
expiretime()andset(get=True)support: New cache methods for retrieving absolute expiry timestamps and atomic get-and-set operations.- Atomic CAS operations in admin: Key detail edits use compare-and-swap via Lua-computed SHA1 fingerprints to prevent concurrent edit conflicts.
- Key detail pagination: Collection types (list, hash, set, zset, stream) are paginated at 100 items per page with
?page=Nnavigation. - Keys in admin sidebar: The key list is now a first-class sidebar entry with a cache filter for switching between configured caches.
- Simplified Lua script execution:
eval_script()replaces theregister_script/LuaScriptregistry with directEVALcalls; redis-py handles script caching. - Async data structure methods: All hash, list, set, and sorted set operations now have async counterparts on
RespCache(e.g.ahset,alpush,asadd,azadd). - Stream operations: Full sync and async support for Redis streams (
xadd,xread,xrange,xlen,xdel,xtrim,xinfo_stream,xgroup_create,xreadgroup,xack,xpending,xclaim,xautoclaim, and more). - Safe
clear():clear()now usesdelete_pattern("*")to only remove keys for the current cache version and prefix, instead ofFLUSHDB. Useflush_db()for the old behavior. - Danger zone in admin: Cache detail view has a "Danger Zone" section with "Clear all versions" and "Flush database" actions. Key list view has a "Clear" button for safe prefix-scoped clearing.
hsetitems param:hset()now accepts anitemsparameter (flat key-value list), matching the redis-py/valkey-py signature.hmsetis removed.delete_patternbatched deletes: Deletes are now batched to prevent OOM on broad patterns.- Multi-key params standardized: Set operations (
sdiff,sinter,sunion, etc.) acceptKeyT | Sequence[KeyT]consistently.
0.2.0 (February 2026)¶
- Django permissions enforced: The admin now uses Django's built-in permission system for granular access control. Staff users need explicit permissions; superusers are unaffected.
0.1.0 (February 2026)¶
Initial stable release of django-cachex.
Features¶
- Valkey and Redis support in one package.
- Session backend support via Django's cache sessions.
- Pluggable clients: Default, Sentinel, Cluster.
- Pluggable serializers: Pickle, JSON, MsgPack.
- Pluggable compressors: Zlib, Gzip, LZMA, LZ4, Zstandard.
- Multi-serializer/compressor fallback for safe migrations.
- Connection pooling with configurable options.
- Primary/replica replication support.
- Valkey/Redis Sentinel support for high availability.
- Valkey/Redis Cluster support with automatic slot handling.
- Distributed locks compatible with
threading.Lock. - TTL operations:
ttl(),pttl(),expire(),persist(). - Pattern operations:
keys(),iter_keys(),delete_pattern(). - Pipelines for batched operations.
- Lua script interface with automatic key prefixing and value encoding/decoding.
- Django Cache Admin for cache inspection and management:
- Browse, search, edit, and delete cache keys.
- View server info, memory statistics, and slowlog.
- Key type filter sidebar.
- Support for Django builtin backends (LocMemCache, DatabaseCache, FileBasedCache) via wrappers.
- Django Unfold theme support (
django_cachex.unfold). - Async support for all extended methods.
Data Structure Operations¶
- Hash operations:
hset,hdel,hexists,hget,hgetall,hincrby,hincrbyfloat,hkeys,hlen,hmget,hmset,hsetnx,hvals - Sorted set operations:
zadd,zcard,zcount,zincrby,zrange,zrevrange,zrangebyscore,zrevrangebyscore,zrank,zrevrank,zrem,zremrangebyrank,zremrangebyscore,zscore,zmscore,zpopmin,zpopmax - List operations:
llen,lpush,rpush,lpop,rpop,lindex,lrange,lset,ltrim,lrem,lpos,linsert,lmove,blpop,brpop,blmove - Set operations:
sadd,srem,smembers,sismember,smismember,scard,spop,srandmember,smove,sdiff,sdiffstore,sinter,sinterstore,sunion,sunionstore,sscan,sscan_iter
Requirements¶
- Python 3.12+
- Django 5.2+
- valkey-py 6.1+ or redis-py 6+
Pre-release History¶
0.1.0b6 (February 2026)¶
New Features¶
- Key type filter: Filter keys by type (string, list, set, hash, zset, stream) in the admin key list sidebar
- LocMemCache data structure operations: List, set, and hash operations now work with LocMemCache wrappers
- LocMemCache type detection: Automatically detects stored Python types (list, set, dict) and maps them to Redis equivalents
KeyTypeStrEnum: Centralized enum for Redis key types, replacing scattered string literals
Improvements¶
- Major admin refactoring: replaced service layer with helpers module, simplified views, restructured templates
- Unified admin views between classic Django admin and Unfold theme
- Added
_cachex_supportClassVar toCacheProtocolfor standardized support level detection - Mixin-based class patching for cache wrappers (replacing intermediate extension classes)
- Extensive dead code cleanup across the codebase
Bug Fixes¶
- Fixed unfold template differences with classic admin
- Fixed
key_typevariable usage in unfold key detail template - Fixed mypy and ty type-checking errors
- Fixed
!rformat spec forKeyTin error messages
0.1.0b5 (February 2026)¶
New Features¶
- Expanded cache backend support: The admin interface now supports Django's builtin cache backends through wrapper classes
LocMemCache: Full support including key listing, TTL inspection, and memory statisticsDatabaseCache: Key listing, TTL inspection, and database statisticsFileBasedCache: File listing (as MD5 hashes) and disk usage statisticsMemcached: Basic stats when available- Django's
RedisCache: Basic support (full features require django-cachex backends)
Improvements¶
- Standardized
info()output format across all wrapped cache backends - Added TTL support (
ttl(),expire(),persist()) for LocMemCache - Improved cache admin UX: operations that aren't supported now fail gracefully instead of hiding UI elements
Bug Fixes¶
- Fixed LocMemCache keys showing "not found" when clicked in admin
- Fixed cache query parameter preservation in key search form
- Fixed editing for wrapped cache backends
0.1.0b4 (January 2026)¶
New Features¶
- Django Cache Admin: Built-in admin interface for cache management
- Browse all configured caches
- Search keys with wildcard patterns
- View and edit cache values (strings, hashes, lists, sets, sorted sets)
- Inspect TTL and modify expiration
- View server info and memory statistics
- Flush individual caches
-
Bulk delete keys
-
Django Unfold Theme Support: Alternative admin styling for django-unfold users
- Use
django_cachex.unfoldinstead ofdjango_cachex.admin -
Consistent styling with Unfold's modern admin theme
-
Example Projects: Added example projects demonstrating various configurations
examples/simple/- Basic setup with ValkeyCache and LocMemCacheexamples/full/- Multiple backends including Sentinel and Clusterexamples/unfold/- Django Unfold theme integration
0.1.0b3 (January 2026)¶
New Features¶
- Lua Script Interface: High-level API for registering and executing Lua scripts with automatic key prefixing and value encoding/decoding
cache.register_script()to register scripts with pre/post processing hookscache.eval_script()andcache.aeval_script()for sync/async executionpipe.eval_script()for pipeline support- Pre-built helpers:
keys_only_pre,full_encode_pre,decode_single_post,decode_list_post ScriptHelpersclass exposesmake_key,encode,decodefor custom hooks- Automatic SHA caching with NOSCRIPT fallback