diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 19ee807a..71e95baf 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.19.0" + ".": "0.20.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 086df29d..3b9ae266 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,82 @@ # Changelog +## [0.20.0](https://github.com/cachekit-io/cachekit-py/compare/v0.19.0...v0.20.0) (2026-09-30) + + +### ⚠ BREAKING CHANGES + +* **reliability:** removed cachekit.reliability.create_decorator_config and, from cachekit.reliability.profiles, get_decorator_kwargs, minimal_reliability_decorator, balanced_reliability_decorator and full_reliability_decorator. Their output raised TypeError when passed to @cache, so no working call is lost. cachekit.decorators.wrapper.create_cache_wrapper no longer accepts undeclared keyword arguments; it raises TypeError instead of ignoring them. +* **decorators:** `FeatureOrchestrator` (exported from `cachekit.decorators.__all__`) no longer defines `generate_correlation_id`, `create_correlation_id`, `set_correlation_id`, `clear_correlation_id`, `start_request`, `end_request`, or the `correlation_tracker` property. `FeatureOrchestrator.handle_cache_error` no longer accepts a `correlation_id` keyword argument. An explicit caller passing `correlation_id=` now flows it through `**extra_context`, and its log record is unchanged. The decorator's own `*_failed` error records no longer carry a `correlation_id` key; that value was a per-call UUID, or `None`, and nothing read it. `src/cachekit/monitoring/correlation_tracking.py` (`CorrelationTracker`, `LoggerIntegratedTracker`, module-level `generate_correlation_id`/`set_correlation_id`/`get_correlation_id`/`clear_correlation_id`/`correlation_context`) is removed entirely. +* **metrics:** `AsyncMetricsCollector.record_cache_operation()` and `FeatureOrchestrator.record_cache_operation()` no longer accept `hit`. Callers that pass it now raise `TypeError`. `FeatureOrchestrator.record_success()` no longer emits a metrics record. +* **config:** presets apply the canonical default TTLs; drop CACHEKIT_DEFAULT_TTL (LAB-4641) ([#318](https://github.com/cachekit-io/cachekit-py/issues/318)) +* **intent:** .secure rejects integrity_checking=False on both paths (LAB-3970) ([#359](https://github.com/cachekit-io/cachekit-py/issues/359)) +* **interop:** Existing deployments that use `ns` or `nsapi` as an interop namespace on Redis, Memcached or File backends worked before this change and must now rename the namespace. Renaming means a full cache miss for that namespace. +* **backends:** if your deployment used cachekit under more than one tenant (a call with no tenant set counts as `default`), purge the Redis entries that earlier releases wrote. In earlier releases a decorated function that resolved Redis from the environment (`CACHEKIT_REDIS_URL`, `REDIS_URL` or the localhost default), or a backend taken from `RedisBackendProvider.get_backend()`, stayed bound to the tenant current when that backend was first obtained, so every tenant's L2 writes through it landed under that one tenant's `t::` prefix. That tenant keeps reading those entries as its own after the upgrade, and some hold another tenant's value. Entries written with `ttl=None`, or kept alive by `refresh_ttl_on_get=True`, never expire, and a no-argument `invalidate_cache()` reaches only those the calling tenant has read in that process since it started, because no key registry recorded them. After the last process running an earlier release has stopped, delete every `t:*` key in each database cachekit uses. Run this with the Python that cachekit is installed in, since it uses cachekit's redis-py dependency; it prints `0 keys left` when it is done: `python -c 'import itertools, sys, redis; r = redis.Redis.from_url(sys.argv[1]); keys = r.scan_iter(match=sys.argv[2], count=1000); [r.unlink(*b) for b in iter(lambda: list(itertools.islice(keys, 1000)), [])]; print(sum(1 for _ in r.scan_iter(match=sys.argv[2], count=1000)), "keys left")' '' 't:*'`. Do not use a `redis-cli --scan` pipeline: a key set through `key=` can contain a newline or a NUL byte, which the pipeline turns into names that match nothing, so the key survives and the pipeline still exits 0. Run `FLUSHDB` instead only if that database is dedicated to cachekit. Then restart every process, because L1 keeps any entry a process read before the purge for up to the function's `ttl` (300 s with `ttl=None`), and expect a cold cache. On a shared database `t:*` also matches any other application's keys that start with `t:`, so run the same command with `'t::*'` in place of `'t:*'` for `default` and for each tenant you have set, the tenant percent-encoded with `urllib.parse.quote(tenant, safe='')`, an `int` or `UUID` tenant as its `str()` first (tenant `org:123` is `'t:org%3A123:*'`). A deployment that only ever used one tenant is unaffected. `RedisBackendProvider.get_backend()` no longer binds its backend to one tenant: the backend follows `tenant_context` on every operation and falls back to the tenant current at the `get_backend()` call only when the calling context has none; for a backend bound to one tenant, construct `cachekit.backends.redis.provider.PerRequestRedisBackend(client, tenant)` directly, with `client` a `redis.Redis`. A no-argument `invalidate_cache()` on the Redis backend now deletes only the calling tenant's L2 entries. Set `tenant_context` only to a `str`, `bytes`, `int` or `uuid.UUID`: the Redis backend checks each operation's tenant and raises `TypeError` for any other type, including a `bool`, an `IntEnum` member and a `bytearray`, so convert an `IntEnum` member with `int()` and a `bytearray` with `bytes()` first. `int` and `UUID` tenants now work, keyed by their `str()` form (`42` and `"42"` share `t:42:`), which no earlier release did. +* **backends:** the decorator namespace "ck" and any "ck:*" namespace are now reserved for cachekit's internal keys and raise ConfigurationError at decoration time. Rename any function cached under namespace="ck" or a "ck:" prefix before upgrading. +* **decorators:** configure the live circuit breaker from @cache(circuit_breaker=...) (LAB-5340) ([#340](https://github.com/cachekit-io/cachekit-py/issues/340)) +* **decorators:** refuse encryption with backend=None — L1-only held plaintext (LAB-4665) ([#322](https://github.com/cachekit-io/cachekit-py/issues/322)) +* **logging:** `cachekit.logging.JsonFormatter` is removed; importing it now raises ImportError. `StructuredLogger.circuit_breaker_state_change` and `StructuredLogger.set_correlation_id` are removed and now raise AttributeError. No deprecation aliases. Use `set_trace_id` / `clear_trace_id` (or `cachekit.monitoring.correlation_tracking`) for request correlation, and your own `logging.Formatter` subclass for JSON output. +* **reliability:** cachekit.reliability.create_optimized_decorator_config is renamed to cachekit.reliability.create_decorator_config. Update imports; no alias is provided. cachekit.hash_utils.fast_hash is renamed to blake3_hash (module-internal, never exported from the package). +* **keys:** cache keys for any serializer other than the default change identity on upgrade — no configuration change is required to be affected. A deployment using `serializer="auto"`, `"orjson"`, `"arrow"`, or any serializer passed as an instance was writing `:1s` keys and will now write `:1a`, `:1o`, `:1w` or an `x`-prefixed code. That function's entire working set recomputes once at deploy, so plan a cold cache or roll out behind existing warm-up / stampede controls. Deployments on the default serializer are unaffected: their keys were already `:1s` and stay `:1s`. Orphaned entries are also a retention question, not only a hit-rate one: once the key changes, `invalidate_cache()` computes the new key and can no longer reach the old copy, so a deletion for erasure, consent withdrawal or permission revocation reports success while the pre-upgrade entry survives to its TTL — or indefinitely where `ttl=None`. Flush the affected namespaces on upgrade if you cache personal data rather than relying on expiry. +* **config:** CachekitConfig drops retry_on_timeout, max_retries, retry_delay_ms, early_refresh_ratio, enable_corruption_detection and max_key_size, and no longer reads CACHEKIT_RETRY_ON_TIMEOUT, CACHEKIT_MAX_RETRIES, CACHEKIT_RETRY_DELAY_MS, CACHEKIT_EARLY_REFRESH_RATIO, CACHEKIT_ENABLE_CORRUPTION_DETECTION or CACHEKIT_MAX_KEY_SIZE. None of them changed behaviour. Passing one to CachekitConfig(...) now raises ValidationError; CachekitConfig ignores a still-exported env var, so startup is unaffected. CachekitIOBackendConfig declares its own max_retries under the same prefix and still parses CACHEKIT_MAX_RETRIES; this release does not change it. Integrity checking is set per decorator (integrity_checking=), refresh-ahead timing by L1CacheConfig.swr_threshold_ratio, and Memcached retries by MemcachedBackendConfig.retry_attempts. +* **decorators:** `CachekitIOBackend(...)` configuration errors (missing, empty or whitespace-containing API key; invalid API URL) now raise `cachekit.config.ConfigurationError` at construction instead of `ValueError` or pydantic `ValidationError`, so code catching `ValueError` there must catch `ConfigurationError`. `@cache.io(backend=...)` and `@cache.io(config=...)` now raise `ConfigurationError` instead of silently ignoring the argument. Under `set_default_backend()`, `@cache(config=...)` now uses the backend inside `config=` instead of the module default. `cachekit.backends.cachekitio.client.get_sync_http_client()` is replaced by `lease_sync_http_client()`, which returns a `SyncClientLease`: hold the lease for as long as its `.client` is used, because the client is closed when the lease is dropped. +* **encryption:** single-tenant tenant_id defaults to "default", not a deployment UUID (LAB-4666) ([#321](https://github.com/cachekit-io/cachekit-py/issues/321)) + +### Features + +* **backends:** server-side key registry for cross-process whole-function invalidation (LAB-651) ([#343](https://github.com/cachekit-io/cachekit-py/issues/343)) ([d918a19](https://github.com/cachekit-io/cachekit-py/commit/d918a1927e64f04f8f1b4f2a64302748a79c76a7)) +* **build:** publish CPython 3.14 wheels (LAB-6297) ([#361](https://github.com/cachekit-io/cachekit-py/issues/361)) ([63fbe14](https://github.com/cachekit-io/cachekit-py/commit/63fbe14773890f94f09d860f4de0c349e817386f)) +* **encryption:** warn once when CACHEKIT_MASTER_KEY auto-activates encryption (LAB-4642) ([#319](https://github.com/cachekit-io/cachekit-py/issues/319)) ([6c8b1da](https://github.com/cachekit-io/cachekit-py/commit/6c8b1daa135e554a6001ddf4de1c014437e54e55)) + + +### Bug Fixes + +* **backends:** scope each Redis operation to the calling tenant, not the first caller (LAB-4773) ([#329](https://github.com/cachekit-io/cachekit-py/issues/329)) ([0386d44](https://github.com/cachekit-io/cachekit-py/commit/0386d44b67d3ed4ce7d260e966d75644a0b8c5dd)) +* **cache_handler:** evict on corrupt CK frame header, not just payload (LAB-4075) ([#307](https://github.com/cachekit-io/cachekit-py/issues/307)) ([ed799c1](https://github.com/cachekit-io/cachekit-py/commit/ed799c133db435a39e87d363474bd23f5ff5624a)) +* **cachekitio:** drain lock POST/DELETE so a cancel cannot orphan a granted lock (LAB-3648) ([#345](https://github.com/cachekit-io/cachekit-py/issues/345)) ([288cb0e](https://github.com/cachekit-io/cachekit-py/commit/288cb0e23be51d018d288deccb468e2822fb0864)) +* **cachekitio:** pin the hpack logger at INFO so DEBUG logs cannot expose the API key (LAB-5914) ([#351](https://github.com/cachekit-io/cachekit-py/issues/351)) ([3738339](https://github.com/cachekit-io/cachekit-py/commit/3738339dcc6d817bf8c4d25cf2231929904fa54b)) +* **cachekitio:** reject API keys outside the RFC 6750 bearer-token charset (LAB-5943) ([#352](https://github.com/cachekit-io/cachekit-py/issues/352)) ([c9c9a0e](https://github.com/cachekit-io/cachekit-py/commit/c9c9a0e4a5f8a9bdb10f2dc792258ec0c0e876a3)) +* **cachekitio:** reject reserved key segments client-side (LAB-2880) ([#364](https://github.com/cachekit-io/cachekit-py/issues/364)) ([44b36f1](https://github.com/cachekit-io/cachekit-py/commit/44b36f1eb943c8e9741233727dd58d1eaf4c024d)) +* **config:** presets apply the canonical default TTLs; drop CACHEKIT_DEFAULT_TTL (LAB-4641) ([#318](https://github.com/cachekit-io/cachekit-py/issues/318)) ([f838c60](https://github.com/cachekit-io/cachekit-py/commit/f838c60a0861710e13a50ccd3d7d0fbcb00b5bf3)) +* **config:** redact inputs from backend config ValidationErrors (LAB-5038) ([#334](https://github.com/cachekit-io/cachekit-py/issues/334)) ([c949454](https://github.com/cachekit-io/cachekit-py/commit/c94945466fe0ff81a3863f2f3d8718fb93d1fc74)) +* **decorators:** [@cache](https://github.com/cache).io accepts api_key= and rejects backend= (LAB-4643) ([#320](https://github.com/cachekit-io/cachekit-py/issues/320)) ([4d36167](https://github.com/cachekit-io/cachekit-py/commit/4d3616759cdf491ef46f9e103b47fa043e217d18)) +* **decorators:** accept l1_enabled on intent presets and keep config= L1 tuning (LAB-4828) ([#358](https://github.com/cachekit-io/cachekit-py/issues/358)) ([3f5c1e0](https://github.com/cachekit-io/cachekit-py/commit/3f5c1e05325c08c3da41eeedcdf1f635982d38bd)) +* **decorators:** async miss-store records carry serializer/hit like sync (LAB-3755) ([#295](https://github.com/cachekit-io/cachekit-py/issues/295)) ([b733196](https://github.com/cachekit-io/cachekit-py/commit/b733196b3f918a436947daa4e86d5208ff021d9c)) +* **decorators:** configure the live circuit breaker from [@cache](https://github.com/cache)(circuit_breaker=...) (LAB-5340) ([#340](https://github.com/cachekit-io/cachekit-py/issues/340)) ([a20cc69](https://github.com/cachekit-io/cachekit-py/commit/a20cc69b6fe2e4fc20d3d9c0e283b8a10445a79b)) +* **decorators:** invalidate_cache resolves the same key the write path wrote (LAB-4387) ([#312](https://github.com/cachekit-io/cachekit-py/issues/312)) ([bebfc1d](https://github.com/cachekit-io/cachekit-py/commit/bebfc1dc9a4a84e1d538fa936122d7c0a7344009)) +* **decorators:** keep a key re-recorded during whole-function invalidation (LAB-5774) ([#348](https://github.com/cachekit-io/cachekit-py/issues/348)) ([c1b125b](https://github.com/cachekit-io/cachekit-py/commit/c1b125bd31c1c487a9b14a1baf5a85e4e191bba9)) +* **decorators:** keep L2 decrypt and integrity failures out of circuit-breaker accounting (LAB-5865) ([1b30f43](https://github.com/cachekit-io/cachekit-py/commit/1b30f43ff124bf995e2c827eea365a503423e4d7)) +* **decorators:** raise TypeError for an unsupported tenant id on sync as on async (LAB-5713) ([6612f39](https://github.com/cachekit-io/cachekit-py/commit/6612f39228eea519a2c6dcc9cccf91b09067100e)) +* **decorators:** refuse encryption with backend=None — L1-only held plaintext (LAB-4665) ([#322](https://github.com/cachekit-io/cachekit-py/issues/322)) ([f5340f6](https://github.com/cachekit-io/cachekit-py/commit/f5340f65b40ff11ca8a918fa0d753bc58eee81f4)) +* **encryption:** classify a non-string original_type header as corruption, not tamper (LAB-4350) ([#310](https://github.com/cachekit-io/cachekit-py/issues/310)) ([2403e7e](https://github.com/cachekit-io/cachekit-py/commit/2403e7ee437e6059c20469ca7237e0c0e2e22fbb)) +* **encryption:** single-tenant tenant_id defaults to "default", not a deployment UUID (LAB-4666) ([#321](https://github.com/cachekit-io/cachekit-py/issues/321)) ([c5da48a](https://github.com/cachekit-io/cachekit-py/commit/c5da48a702840698546bcc1e369c1c475c7dcfa2)) +* **encryption:** surface keyring config faults as KeyringConfigurationError (LAB-4818) ([93cafd3](https://github.com/cachekit-io/cachekit-py/commit/93cafd34c362b2cdf8c0d7656158e1ffad18178f)) +* **intent:** .secure rejects integrity_checking=False on both paths (LAB-3970) ([#359](https://github.com/cachekit-io/cachekit-py/issues/359)) ([4f25185](https://github.com/cachekit-io/cachekit-py/commit/4f25185021db2fc9dd7f9c6707236221fb1dffd3)) +* **interop:** key str-subclass segments by their exact str value (LAB-6196) ([#360](https://github.com/cachekit-io/cachekit-py/issues/360)) ([d067787](https://github.com/cachekit-io/cachekit-py/commit/d067787c200f3b650fc571883047c46970a9d405)) +* **interop:** reject reserved namespaces ns and nsapi (LAB-5876) ([#350](https://github.com/cachekit-io/cachekit-py/issues/350)) ([6ba98a6](https://github.com/cachekit-io/cachekit-py/commit/6ba98a692e422cd19fa29fda51841ee8997547c7)) +* **keys:** put the real serializer identity in the cache key (LAB-4351) ([#311](https://github.com/cachekit-io/cachekit-py/issues/311)) ([ee65250](https://github.com/cachekit-io/cachekit-py/commit/ee65250b9d9f2d8842eb760c0197190bd0b5b0a0)) +* **l1:** restart the background cleanup thread in forked children (LAB-4772) ([#328](https://github.com/cachekit-io/cachekit-py/issues/328)) ([593c194](https://github.com/cachekit-io/cachekit-py/commit/593c194ac718733fc719bad70e71f904abf41ce8)) +* **metrics:** drain queued records before batched worker exits (LAB-6351) ([#365](https://github.com/cachekit-io/cachekit-py/issues/365)) ([385229b](https://github.com/cachekit-io/cachekit-py/commit/385229ba7a99519be5263d1cfa2b488dde533ec7)) +* **metrics:** record each cache operation once in cache_operations_total (LAB-3761) ([#346](https://github.com/cachekit-io/cachekit-py/issues/346)) ([d317494](https://github.com/cachekit-io/cachekit-py/commit/d317494586dc2e89e709d62ba96d075086e12e90)) +* **metrics:** share Prometheus metric objects across collectors (LAB-6355) ([#366](https://github.com/cachekit-io/cachekit-py/issues/366)) ([e41c868](https://github.com/cachekit-io/cachekit-py/commit/e41c8681c30de4262c092ec8a19a25415b1b49dd)) +* **redis:** classify ClusterDownError as TRANSIENT (LAB-5327) ([#339](https://github.com/cachekit-io/cachekit-py/issues/339)) ([e93b693](https://github.com/cachekit-io/cachekit-py/commit/e93b693ef6bb93fd0db8b4167c66ad0223bb13db)) +* **redis:** classify TryAgainError transient, InvalidResponse/LockError permanent (LAB-5353) ([#344](https://github.com/cachekit-io/cachekit-py/issues/344)) ([fb9baa6](https://github.com/cachekit-io/cachekit-py/commit/fb9baa6c96f72326eec778f78c4602d9aebc4397)) +* **redis:** release a lock won after acquire_lock cancellation (LAB-3606) ([#293](https://github.com/cachekit-io/cachekit-py/issues/293)) ([4cfdc63](https://github.com/cachekit-io/cachekit-py/commit/4cfdc63a67991f117d75275feb57912ab861b4b7)) +* **reliability:** circuit breaker recovers from OPEN on the decorator path (LAB-5326) ([#341](https://github.com/cachekit-io/cachekit-py/issues/341)) ([9182092](https://github.com/cachekit-io/cachekit-py/commit/918209277f8155e89cdaef9e925f063b85506c68)) +* **serializers:** integrity-off AutoSerializer refuses envelope-shaped values at write (LAB-6401) ([#377](https://github.com/cachekit-io/cachekit-py/issues/377)) ([ea1a039](https://github.com/cachekit-io/cachekit-py/commit/ea1a039c60a87995d97c2c01c50c859f823ed52e)) +* **serializers:** refuse verified envelopes on integrity-off StandardSerializer reads (LAB-4329) ([#379](https://github.com/cachekit-io/cachekit-py/issues/379)) ([9502662](https://github.com/cachekit-io/cachekit-py/commit/950266251ab5bdb4e1d22740b9f65376f8722221)) +* **serializers:** settle the envelope format by agreement, not by a winner (LAB-2736) ([#309](https://github.com/cachekit-io/cachekit-py/issues/309)) ([165c9b5](https://github.com/cachekit-io/cachekit-py/commit/165c9b5764836ec677c3130bef997a5f37ec308e)) +* **serializers:** treat an entry with no serializer name as a mismatch (LAB-4432) ([#373](https://github.com/cachekit-io/cachekit-py/issues/373)) ([b3869a7](https://github.com/cachekit-io/cachekit-py/commit/b3869a7dd10af049e86dc870a7176ae09220f6a8)) + + +### Code Refactoring + +* **config:** remove six CachekitConfig knobs nothing reads (LAB-4740) ([#324](https://github.com/cachekit-io/cachekit-py/issues/324)) ([27e1f95](https://github.com/cachekit-io/cachekit-py/commit/27e1f95dc727e1b2c03ebed8c4dfd42f7caa1ad2)) +* **decorators:** remove dead correlation-ID surface (LAB-4733) ([#323](https://github.com/cachekit-io/cachekit-py/issues/323)) ([841dba6](https://github.com/cachekit-io/cachekit-py/commit/841dba64bd1e0fd679296a554bdef993bb583623)) +* **logging:** remove circuit_breaker_state_change, set_correlation_id and JsonFormatter (LAB-4638) ([#317](https://github.com/cachekit-io/cachekit-py/issues/317)) ([0a71361](https://github.com/cachekit-io/cachekit-py/commit/0a71361593a44f1df9ba7f7d14f73a1cf0795022)) +* **reliability:** remove create_decorator_config family (LAB-4637) ([#338](https://github.com/cachekit-io/cachekit-py/issues/338)) ([4214448](https://github.com/cachekit-io/cachekit-py/commit/4214448c6b85ebd4362085370c7c9801b181fe5b)) +* **reliability:** rename create_optimized_decorator_config and fast_hash (LAB-4619) ([#315](https://github.com/cachekit-io/cachekit-py/issues/315)) ([8d49e38](https://github.com/cachekit-io/cachekit-py/commit/8d49e38752b2c5e1a2c552b18b28f1f2d8eb4cd2)) + ## [0.19.0](https://github.com/cachekit-io/cachekit-py/compare/v0.18.0...v0.19.0) (2026-09-22) diff --git a/Cargo.lock b/Cargo.lock index 12aee7d8..415cc679 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -271,7 +271,7 @@ dependencies = [ [[package]] name = "cachekit-rs" -version = "0.19.0" +version = "0.20.0" dependencies = [ "cachekit-core", "criterion", diff --git a/pyproject.toml b/pyproject.toml index 673d04e9..d27dee2f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "cachekit" -version = "0.19.0" +version = "0.20.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 457500ca..03acd5f0 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cachekit-rs" -version = "0.19.0" +version = "0.20.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 5a78be31..0becb260 100644 --- a/src/cachekit/__init__.py +++ b/src/cachekit/__init__.py @@ -69,7 +69,7 @@ def custom_function(): ``` """ -__version__ = "0.19.0" +__version__ = "0.20.0" from collections.abc import Callable from typing import Any, TypeVar diff --git a/uv.lock b/uv.lock index 3827e435..036a47ab 100644 --- a/uv.lock +++ b/uv.lock @@ -249,7 +249,7 @@ filecache = [ [[package]] name = "cachekit" -version = "0.19.0" +version = "0.20.0" source = { editable = "." } dependencies = [ { name = "anyio", version = "4.14.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" },