Releases: toloco/warp_cache
0.7.0
Changelog
Improve typing coverage with ParamSpec + Protocol (#27)
Type checkers now see cache_info() and cache_clear() on decorated functions, while preserving the original call signature:
- Added
CachedCallable[P, R]protocol usingParamSpec— decorated functions retain their arg/return types - Added
BaseCacheInfoprotocol for the common cache info interface (hits, misses, max_size, current_size) cache()return type updated fromCallable[[F], F]toCallable[[Callable[P, R]], CachedCallable[P, R]]- Added
_probeto_warp_cache_rs.pyistub typing_extensionsdependency added for Python 3.9 only
Document key serialization behavior (#28)
Added "Key serialization behavior" section to docs/usage.md:
- How cache keys are formed from
*args/**kwargs(sorted kwargs for determinism) - Memory backend: Python objects,
hash(), single-process - Shared backend: fast-path binary format for primitives, pickle fallback
- Cross-process determinism: fixed-seed ahash, immune to
PYTHONHASHSEED
0.6.0
Changelog
Async single-flight coalescing — dogpile prevention (#25)
When multiple async coroutines miss the cache for the same key concurrently, only one computes the result — the rest await and reuse it. Works for both memory and shared backends, default on for all async cached functions.
- Error recovery: if the computing coroutine fails, a waiter automatically becomes the new leader
- Cancellation safe: cancelled leaders don't block waiters
- Zero Rust changes — implemented via
asyncio.Eventin the Python async wrapper
Fix async caching of None return values (#26)
Previously, async-wrapped functions returning None were never cached because get() couldn't distinguish "cache miss" from "cached None" (PyO3's Option<Py<PyAny>> conflates both to Python None).
- Added
_probe()method to both backends returning(hit: bool, value) AsyncCachedFunctionnow uses_probe()instead ofget()get()unchanged for backwards compatibility
Housekeeping
0.5.0
Changelog
Replace LRU/MRU/FIFO/LFU with SIEVE eviction (#20)
The biggest change — unified eviction to SIEVE across both backends:
- Removed src/strategies/ module entirely (~540 lines deleted): LRU, MRU, FIFO, LFU all gone
- Removed Strategy enum and strategy= parameter from the Python API
- Shared memory backend: reads are now fully lock-free (visited bit set via idempotent store, no write lock on hits)
- Layout VERSION bumped 2 → 3 (forces recreation of old mmap files)
- Performance gains (shared backend):
- Single-process: 7.8M → 8.9M ops/s
- Multi-process 4 procs: 3.1M → 7.7M ops/s (2.5x)
- Multi-process 8 procs: 1.9M → 6.5M ops/s (3.4x)
- Hit rate: 64.9% → 72.7% (scan-resistant eviction)
- Updated all docs, examples, benchmarks, and llms.txt
PassthroughHasher, BorrowedArgs, GilCell, free-threaded Python support (#21)
Major performance optimization of the in-process memory backend:
- PassthroughHasher: feeds Python's precomputed hash directly to hashbrown (skips foldhash re-hash, ~1-2ns saved per lookup)
- BorrowedArgs: zero-alloc borrowed key for hit-path lookups via hashbrown's Equivalent trait — no CacheKey allocation or refcount churn on cache hits
- GilCell: zero-cost UnsafeCell wrapper under GIL-enabled Python, replaced by parking_lot::RwLock under #[cfg(Py_GIL_DISABLED)] for free-threaded Python support
- Sharded HashMap: per-shard locking enables true parallel reads under free-threaded Python
Bump PyO3 0.28.1 → 0.28.2 (#19)
- Routine dependency update via Dependabot
Add Python 3.14 support (#22)
- Added 3.14 to CI test matrix, publish workflow, classifiers, and benchmark defaults
0.2.1
0.2.0
Bug Fixes
-
Fix cross-process hashing for string/bytes keys (CRITICAL): The shared
memory backend previously computed key hashes from Python'shash(), which is
randomized per process (PYTHONHASHSEED) forstrandbytestypes. This
caused cross-process cache lookups to silently miss — breaking the core value
proposition of the shared backend. Key hashes are now computed from the
deterministic serialized bytes usingahashwith fixed seeds. -
Fix oversize early-exit check: The
__call__path in
SharedCachedFunctioncomparedkey_bytes.len()againstmax_size(the
maximum entry count), which almost never triggered. The check now correctly
delegates tois_oversize()only.
Improvements
-
Document approximate eviction ordering: Added doc comments to the internal
ACCESS_LOG_CAPACITYconstant and aNote:block to thecache()decorator
docstring explaining that eviction ordering (LRU, MRU, LFU) is approximate
under sustained hit-only workloads due to batched ordering updates. -
Add PEP 561 typing support: Added
py.typedmarker and
_warp_cache_rs.pyitype stubs forCacheInfo,SharedCacheInfo,
CachedFunction, andSharedCachedFunction. Type checkers (mypy, pyright)
can now understand the Rust extension's API.
Tests
- Add cross-process string key test: New
test_cross_process_str_key_different_hashseedspawns a subprocess with an
explicitPYTHONHASHSEED=12345to verify that string keys written by one
process are correctly found by another — directly validating the hashing fix.
Other
- Fix repository URL in
pyproject.toml(tolo→toloco). - Add
ahash = "0.8"dependency (Unix targets only).