diff --git a/.release-please-manifest.json b/.release-please-manifest.json index d5d3f370..c84411f5 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.25.1" + ".": "0.26.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 36ebf99b..76ef045e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,52 @@ # Changelog +## [0.26.0](https://github.com/cachekit-io/cachekit-py/compare/v0.25.1...v0.26.0) (2026-11-08) + + +### ⚠ BREAKING CHANGES + +* **decorators:** an L1-only cache (`@cache(backend=None)`, `@cache.local()`, or any plaintext, non-interop decorator that finds no backend configured) times its TTLs and its stale-while-revalidate refresh on the OS monotonic clock that the Rust extension reads, not on `time.monotonic()`. Patching `time.monotonic` (pytest's `monkeypatch`, freezegun's `freeze_time` or `tick`) no longer expires its entries or brings a refresh due: a test that needs an entry gone calls `fn.invalidate_cache(...)` or `fn.cache_clear()`. Caches with a backend are unchanged. + +### Features + +* **cachekitio:** report L1 hit latency and add a telemetry off switch (LAB-12079) ([#691](https://github.com/cachekit-io/cachekit-py/issues/691)) ([0cc1a9c](https://github.com/cachekit-io/cachekit-py/commit/0cc1a9ca0a5ddd81e3eabf3878d64adf81d89d5d)) +* **decorators:** let async [@cache](https://github.com/cache) opt out of the miss-path distributed lock (LAB-7166) ([#667](https://github.com/cachekit-io/cachekit-py/issues/667)) ([1e2295d](https://github.com/cachekit-io/cachekit-py/commit/1e2295d5f0a6ed894805685caa30cdcae5b3efc0)) + + +### Bug Fixes + +* **decorators:** build an L1-only memoised key from one read of the names (LAB-12946) ([#720](https://github.com/cachekit-io/cachekit-py/issues/720)) ([7b06953](https://github.com/cachekit-io/cachekit-py/commit/7b06953fce4ba61f6508149b044cfebc549fd61d)) +* **decorators:** clear an L1-only cache whole on invalidate-all and let the memo own the kill (LAB-12024) ([#677](https://github.com/cachekit-io/cachekit-py/issues/677)) ([5aeaa63](https://github.com/cachekit-io/cachekit-py/commit/5aeaa63249d869b114963ea952677eea2bdda41c)) +* **decorators:** clear every [@cache](https://github.com/cache).local() entry on a no-args invalidate_cache() (LAB-12618) ([#714](https://github.com/cachekit-io/cachekit-py/issues/714)) ([720a5d1](https://github.com/cachekit-io/cachekit-py/commit/720a5d1fe28dba26fab892b3902fb3732be925d8)) +* **decorators:** drop a fill's store when an invalidation in this process overlaps it (LAB-12149) ([#713](https://github.com/cachekit-io/cachekit-py/issues/713)) ([d99bf24](https://github.com/cachekit-io/cachekit-py/commit/d99bf249fb77b9a23ad456fa243dbf74cdf3ea1e)) +* **decorators:** scope the L2 invalidation gate by tenant (LAB-12617) ([#715](https://github.com/cachekit-io/cachekit-py/issues/715)) ([a6f8f76](https://github.com/cachekit-io/cachekit-py/commit/a6f8f7636555bf6fcf277332765a980c9429c6d5)) +* **metrics:** skip thread-state retirement once the interpreter is exiting (LAB-12087) ([#679](https://github.com/cachekit-io/cachekit-py/issues/679)) ([42d7aea](https://github.com/cachekit-io/cachekit-py/commit/42d7aea89d21f9c3a887cee57dd391a5ef35f9c4)) +* **monitoring:** make enable_prometheus_metrics=False stop recording (LAB-12038) ([#681](https://github.com/cachekit-io/cachekit-py/issues/681)) ([3d81a93](https://github.com/cachekit-io/cachekit-py/commit/3d81a9369bc0e25690f5a69270521eacfb0a25ef)) +* **perf:** name the cachekit, Python and CPU a terrain recording ran (LAB-11993) ([#687](https://github.com/cachekit-io/cachekit-py/issues/687)) ([251a2fa](https://github.com/cachekit-io/cachekit-py/commit/251a2fad4fe50aef1e5275f956d8a4b11cf33e7c)) +* **perf:** terrain canary records the L2 label without its padding (LAB-11993) ([#676](https://github.com/cachekit-io/cachekit-py/issues/676)) ([4b2c65c](https://github.com/cachekit-io/cachekit-py/commit/4b2c65cca5b2e832dcf9cce840669be16bb613f7)) +* **rust:** decode StandardSerializer MessagePack in safe Rust (LAB-12250) ([#694](https://github.com/cachekit-io/cachekit-py/issues/694)) ([53bd1f3](https://github.com/cachekit-io/cachekit-py/commit/53bd1f3df867849309e038933054fd47666ee897)) +* **serializers:** bound AutoSerializer set revival against hash floods (LAB-12954) ([9e3a3e7](https://github.com/cachekit-io/cachekit-py/commit/9e3a3e783b6953d18dee822270074470173cc731)) +* **serializers:** refuse AutoSerializer datetimes that read back as another value (LAB-12972) ([#723](https://github.com/cachekit-io/cachekit-py/issues/723)) ([53591b6](https://github.com/cachekit-io/cachekit-py/commit/53591b6f12293ca9b01ab96add607f0a7ca06dae)) + + +### Performance Improvements + +* **decorators:** a backed L1 hit skips the stats context and log kwargs (LAB-12115) ([#686](https://github.com/cachekit-io/cachekit-py/issues/686)) ([9c1bd7f](https://github.com/cachekit-io/cachekit-py/commit/9c1bd7f6fc2798683e69c4fdb88bf2a8b784acdb)) +* **decorators:** answer a [@cache](https://github.com/cache).local() hit in one lock-free HitMemo call (LAB-12916) ([#718](https://github.com/cachekit-io/cachekit-py/issues/718)) ([487d042](https://github.com/cachekit-io/cachekit-py/commit/487d042bfb89991d6b55e98a427d57c479152c7b)) +* **decorators:** answer the L1-only hit in one lock-free call into the extension (LAB-12024) ([#673](https://github.com/cachekit-io/cachekit-py/issues/673)) ([8b17e51](https://github.com/cachekit-io/cachekit-py/commit/8b17e5109a00b3332252af40930c9cd966eb51e8)) +* **deps:** bump cachekit-core to 0.7.4 (LAB-12129) ([#684](https://github.com/cachekit-io/cachekit-py/issues/684)) ([9485ca8](https://github.com/cachekit-io/cachekit-py/commit/9485ca862dc23acafc9ae1a74d62cf9787c14671)) +* **ir:** make measured startup the same in every run and re-record every budget (LAB-12297) ([#699](https://github.com/cachekit-io/cachekit-py/issues/699)) ([7146959](https://github.com/cachekit-io/cachekit-py/commit/71469596593cbd5adbd54423fee0d1463d0f4525)) +* **ir:** run the 2,600 Ir/op paths over 300,000 calls (LAB-12245) ([#697](https://github.com/cachekit-io/cachekit-py/issues/697)) ([0e15cb6](https://github.com/cachekit-io/cachekit-py/commit/0e15cb6495a491edfbafe14915da8280974d6367)) +* **l1:** auto and orjson L1 hits skip re-verification and the per-read copy (LAB-12119) ([#696](https://github.com/cachekit-io/cachekit-py/issues/696)) ([9ce58b6](https://github.com/cachekit-io/cachekit-py/commit/9ce58b6f6c5455640473c8eeccd3109181578070)) +* **l1:** skip the structure walk and object hook on a kept L1 entry (LAB-12023) ([#671](https://github.com/cachekit-io/cachekit-py/issues/671)) ([7fbdfbe](https://github.com/cachekit-io/cachekit-py/commit/7fbdfbee51268198fae736245653a44f8fd7b2c8)) +* **l1:** sweep expired entries from a deadline heap in short lock holds (LAB-12117) ([#683](https://github.com/cachekit-io/cachekit-py/issues/683)) ([63cf745](https://github.com/cachekit-io/cachekit-py/commit/63cf7452ec3c853a883826b0a25c71b2cc2a31b6)) +* **memcached:** pick the server natively, as pymemcache's rendezvous hash does (LAB-12116) ([#680](https://github.com/cachekit-io/cachekit-py/issues/680)) ([a79cc90](https://github.com/cachekit-io/cachekit-py/commit/a79cc90323f3a0685492efeba29e7fb0feaf778b)) +* **object-cache:** compact dead records a batch per store, not in one lock hold (LAB-12920) ([#717](https://github.com/cachekit-io/cachekit-py/issues/717)) ([140a078](https://github.com/cachekit-io/cachekit-py/commit/140a0783e1b45ec98a731589ac3439c9821d1d56)) +* **redis:** an uncontended async miss waits on 3 round trips, not 5 (LAB-12118) ([#682](https://github.com/cachekit-io/cachekit-py/issues/682)) ([1ef1d05](https://github.com/cachekit-io/cachekit-py/commit/1ef1d05eafa112d6cf5f73cce86fb7d2804ff119)) +* **serializers:** decode AutoSerializer and interop reads in Rust, bounds and decode in one pass (LAB-12917) ([#719](https://github.com/cachekit-io/cachekit-py/issues/719)) ([27dfe18](https://github.com/cachekit-io/cachekit-py/commit/27dfe183e05af617027d7c9b55e046ef4ac46b6e)) +* **serializers:** decode StandardSerializer reads in Rust, bounds and decode in one pass (LAB-12030) ([#674](https://github.com/cachekit-io/cachekit-py/issues/674)) ([39b1bdf](https://github.com/cachekit-io/cachekit-py/commit/39b1bdf6d1ca59256e1d36f98a4ff4b7dbc4f9fc)) +* **write-behind:** park an idle writer thread instead of starting one per miss (LAB-12005) ([#692](https://github.com/cachekit-io/cachekit-py/issues/692)) ([96141b7](https://github.com/cachekit-io/cachekit-py/commit/96141b7c6c5ccd15211815767f4e12e7ff44a3dc)) + ## [0.25.1](https://github.com/cachekit-io/cachekit-py/compare/v0.25.0...v0.25.1) (2026-10-30) diff --git a/Cargo.lock b/Cargo.lock index 64962685..ed987d0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -221,7 +221,7 @@ dependencies = [ [[package]] name = "cachekit-rs" -version = "0.25.1" +version = "0.26.0" dependencies = [ "cachekit-core", "criterion 0.8.2", diff --git a/pyproject.toml b/pyproject.toml index f84f96bb..e8004360 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "maturin" [project] name = "cachekit" -version = "0.25.1" +version = "0.26.0" description = "Backend-agnostic caching for Python — intent-based decorators with circuit breaker, distributed locking, Prometheus metrics, and optional zero-knowledge AES-256-GCM encryption, on a Rust-powered core. Zero-config L1 in-memory; scales to Redis, Memcached, File, or CachekitIO." readme = "README.md" license = {text = "MIT"} diff --git a/rust/Cargo.toml b/rust/Cargo.toml index ffe674ff..1f5a57de 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cachekit-rs" -version = "0.25.1" +version = "0.26.0" edition = "2021" authors = ["cachekit Contributors"] description = "High-performance storage engine for caching with compression and encryption" diff --git a/src/cachekit/__init__.py b/src/cachekit/__init__.py index e1fd31b2..193d96c3 100644 --- a/src/cachekit/__init__.py +++ b/src/cachekit/__init__.py @@ -79,7 +79,7 @@ def custom_function(): - ``api_key``: a CacheKit Cloud API key, for example ``api_key = os.environ["CACHEKIT_API_KEY"]``. """ -__version__ = "0.25.1" +__version__ = "0.26.0" from collections.abc import Callable from typing import Any, TypeVar diff --git a/uv.lock b/uv.lock index 988b68ee..896135ec 100644 --- a/uv.lock +++ b/uv.lock @@ -222,7 +222,7 @@ filecache = [ [[package]] name = "cachekit" -version = "0.25.1" +version = "0.26.0" source = { editable = "." } dependencies = [ { name = "blake3" },