cachekit 0.25.1__tar.gz → 0.26.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. {cachekit-0.25.1 → cachekit-0.26.0}/Cargo.lock +5 -5
  2. {cachekit-0.25.1 → cachekit-0.26.0}/PKG-INFO +47 -39
  3. {cachekit-0.25.1 → cachekit-0.26.0}/README.md +46 -38
  4. {cachekit-0.25.1 → cachekit-0.26.0}/pyproject.toml +1 -1
  5. {cachekit-0.25.1 → cachekit-0.26.0}/rust/Cargo.toml +1 -1
  6. {cachekit-0.25.1 → cachekit-0.26.0}/rust/README.md +46 -38
  7. cachekit-0.26.0/rust/src/l1_hit.rs +588 -0
  8. {cachekit-0.25.1 → cachekit-0.26.0}/rust/src/lib.rs +48 -4
  9. cachekit-0.26.0/rust/src/msgpack_decode.rs +687 -0
  10. {cachekit-0.25.1 → cachekit-0.26.0}/rust/src/python_bindings.rs +61 -6
  11. cachekit-0.26.0/rust/src/rendezvous.rs +161 -0
  12. cachekit-0.26.0/rust/src/short_bytes.rs +90 -0
  13. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/__init__.py +5 -5
  14. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/backends/base.py +1 -1
  15. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/backends/cachekitio/backend.py +60 -32
  16. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/backends/cachekitio/config.py +90 -1
  17. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/backends/cachekitio/error_handler.py +1 -1
  18. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/backends/memcached/backend.py +4 -0
  19. cachekit-0.26.0/src/cachekit/backends/memcached/hasher.py +35 -0
  20. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/backends/redis/provider.py +77 -10
  21. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/cache_handler.py +98 -45
  22. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/config/decorator.py +15 -3
  23. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/config/nested.py +6 -2
  24. cachekit-0.26.0/src/cachekit/decorators/l1_latency.py +206 -0
  25. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/decorators/local_wrapper.py +89 -17
  26. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/decorators/wrapper.py +388 -337
  27. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/decorators/write_behind.py +65 -11
  28. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/interop.py +24 -9
  29. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/l1_cache.py +155 -34
  30. cachekit-0.26.0/src/cachekit/object_cache.py +1071 -0
  31. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/reliability/async_metrics.py +7 -0
  32. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/serializers/auto_serializer.py +240 -44
  33. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/serializers/base.py +32 -2
  34. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/serializers/orjson_serializer.py +54 -26
  35. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/serializers/standard_serializer.py +29 -48
  36. cachekit-0.25.1/src/cachekit/object_cache.py +0 -649
  37. {cachekit-0.25.1 → cachekit-0.26.0}/Cargo.toml +0 -0
  38. {cachekit-0.25.1 → cachekit-0.26.0}/LICENSE +0 -0
  39. {cachekit-0.25.1 → cachekit-0.26.0}/rust/Makefile +0 -0
  40. {cachekit-0.25.1 → cachekit-0.26.0}/rust/TEST_EXPANSION_SUMMARY.md +0 -0
  41. {cachekit-0.25.1 → cachekit-0.26.0}/rust/src/hit_counter.rs +0 -0
  42. {cachekit-0.25.1 → cachekit-0.26.0}/rust/tsan_suppressions.txt +0 -0
  43. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/_fork.py +0 -0
  44. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/backends/__init__.py +0 -0
  45. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/backends/_uninterrupted.py +0 -0
  46. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/backends/base_config.py +0 -0
  47. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/backends/cachekitio/__init__.py +0 -0
  48. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/backends/cachekitio/client.py +0 -0
  49. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/backends/cachekitio/session.py +0 -0
  50. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/backends/errors.py +0 -0
  51. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/backends/file/__init__.py +0 -0
  52. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/backends/file/backend.py +0 -0
  53. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/backends/file/config.py +0 -0
  54. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/backends/memcached/__init__.py +0 -0
  55. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/backends/memcached/config.py +0 -0
  56. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/backends/memcached/error_handler.py +0 -0
  57. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/backends/provider.py +0 -0
  58. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/backends/redis/__init__.py +0 -0
  59. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/backends/redis/backend.py +0 -0
  60. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/backends/redis/client.py +0 -0
  61. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/backends/redis/config.py +0 -0
  62. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/backends/redis/error_handler.py +0 -0
  63. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/config/__init__.py +0 -0
  64. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/config/settings.py +0 -0
  65. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/config/singleton.py +0 -0
  66. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/config/validation.py +0 -0
  67. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/decorators/__init__.py +0 -0
  68. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/decorators/intent.py +0 -0
  69. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/decorators/main.py +0 -0
  70. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/decorators/orchestrator.py +0 -0
  71. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/decorators/session.py +0 -0
  72. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/decorators/single_flight.py +0 -0
  73. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/decorators/stats_context.py +0 -0
  74. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/decorators/tenant_context.py +0 -0
  75. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/decorators/utils/__init__.py +0 -0
  76. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/di.py +0 -0
  77. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/hash_utils.py +0 -0
  78. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/health.py +0 -0
  79. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/hiredis_compat.py +0 -0
  80. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/imports.py +0 -0
  81. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/invalidation.py +0 -0
  82. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/key_generator.py +0 -0
  83. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/logging.py +0 -0
  84. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/monitoring/__init__.py +0 -0
  85. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/monitoring/protocols.py +0 -0
  86. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/py.typed +0 -0
  87. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/reliability/__init__.py +0 -0
  88. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/reliability/circuit_breaker.py +0 -0
  89. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/reliability/error_classification.py +0 -0
  90. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/reliability/load_control.py +0 -0
  91. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/reliability/metrics_collection.py +0 -0
  92. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/serializers/__init__.py +0 -0
  93. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/serializers/arrow_serializer.py +0 -0
  94. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/serializers/encryption_wrapper.py +0 -0
  95. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/serializers/interop_serializer.py +0 -0
  96. {cachekit-0.25.1 → cachekit-0.26.0}/src/cachekit/serializers/wrapper.py +0 -0
@@ -198,9 +198,9 @@ checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
198
198
 
199
199
  [[package]]
200
200
  name = "cachekit-core"
201
- version = "0.7.2"
201
+ version = "0.7.4"
202
202
  source = "registry+https://github.com/rust-lang/crates.io-index"
203
- checksum = "101672176881ef05b17e8399476eb6fc2082e6a5c33788c84c9bb4d1a8d512d7"
203
+ checksum = "d9217fe47d4024d7b0d1dda460bbcfc0f613bd80fb94c4fbbc4e7e6d829d7342"
204
204
  dependencies = [
205
205
  "aes",
206
206
  "aes-gcm",
@@ -221,7 +221,7 @@ dependencies = [
221
221
 
222
222
  [[package]]
223
223
  name = "cachekit-rs"
224
- version = "0.25.1"
224
+ version = "0.26.0"
225
225
  dependencies = [
226
226
  "cachekit-core",
227
227
  "criterion 0.8.2",
@@ -892,9 +892,9 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
892
892
 
893
893
  [[package]]
894
894
  name = "lz4_flex"
895
- version = "0.12.2"
895
+ version = "0.13.1"
896
896
  source = "registry+https://github.com/rust-lang/crates.io-index"
897
- checksum = "90071f8077f8e40adfc4b7fe9cd495ce316263f19e75c2211eeff3fdf475a3d9"
897
+ checksum = "7ef0d4ed8669f8f8826eb00dc878084aa8f253506c4fd5e8f58f5bce72ddb97e"
898
898
 
899
899
  [[package]]
900
900
  name = "memchr"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cachekit
3
- Version: 0.25.1
3
+ Version: 0.26.0
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -87,15 +87,17 @@ def expensive_function():
87
87
  return fetch_data()
88
88
  ```
89
89
 
90
- That's it. You get:
90
+ With nothing configured, that plain `@cache` caches in process memory (L1) only: no backend,
91
+ no distributed lock, no encryption. Configure a backend and pick a [preset](#intent-based-optimization),
92
+ and you get:
91
93
 
92
94
  | Feature | Description |
93
95
  |:--------|:------------|
94
96
  | **Circuit breaker** | Prevents cascading failures |
95
- | **Distributed locking** | Multi-pod safety |
97
+ | **Distributed locking** | Multi-pod safety (async functions, on a backend that supports it) |
96
98
  | **Prometheus metrics** | Built-in observability |
97
99
  | **MessagePack serialization** | Efficient with optional compression |
98
- | **Zero-knowledge encryption** | Client-side AES-256-GCM |
100
+ | **Zero-knowledge encryption** | Client-side AES-256-GCM, opt-in (`@cache.secure`) |
99
101
 
100
102
  ---
101
103
 
@@ -137,7 +139,7 @@ backend that fits your infrastructure — they're peers behind the same `@cache`
137
139
  | Backend | Best for | Select with |
138
140
  |---------|----------|-------------|
139
141
  | Redis | Self-hosted, full control | `REDIS_URL` / `CACHEKIT_REDIS_URL` |
140
- | CachekitIO | Managed, zero-ops (beta) | `CACHEKIT_API_KEY` |
142
+ | CacheKit Cloud | Managed, zero-ops (closed beta) | `CACHEKIT_API_KEY` |
141
143
  | Memcached | High-throughput, existing infra | `CACHEKIT_MEMCACHED_SERVERS` |
142
144
  | File / L1-only | Local dev, tests, no external deps | `CACHEKIT_FILE_CACHE_DIR` / nothing (or `backend=None`) |
143
145
 
@@ -155,31 +157,36 @@ def expensive_api_call(user_id: int):
155
157
  ```
156
158
 
157
159
  > [!WARNING]
158
- > Earlier releases connected to `redis://localhost:6379` when nothing was configured. To keep
159
- > that Redis, set `REDIS_URL=redis://localhost:6379` (or `CACHEKIT_REDIS_URL`). `@cache.secure`,
160
- > `encryption=True` and `interop=` still need a backend and still fall back to localhost Redis;
161
- > see [Backend Resolution Priority](docs/backends/README.md#backend-resolution-priority).
160
+ > With nothing configured, `@cache` never connects to `redis://localhost:6379`. To use that
161
+ > Redis, set `REDIS_URL=redis://localhost:6379` (or `CACHEKIT_REDIS_URL`); upgraders, see the
162
+ > [CHANGELOG](CHANGELOG.md). `@cache.secure`
163
+ > and `encryption=True` still need a backend and still fall back to localhost Redis; see
164
+ > [Backend Resolution Priority](docs/backends/README.md#backend-resolution-priority). `interop=`
165
+ > refuses that Redis, and any Redis resolved without `backend=`, because it prefixes keys with the
166
+ > tenant: pass `backend=RedisBackend()` ([Interop Mode](docs/features/interop-mode.md)).
162
167
 
163
168
  ### More Backends
164
169
 
165
170
  <details>
166
- <summary><strong>CachekitIO — Managed SaaS (Beta)</strong></summary>
171
+ <summary><strong>CacheKit Cloud — Managed (Closed Beta)</strong></summary>
167
172
 
168
173
  ```python notest
169
174
  import os
170
175
  from cachekit import cache
171
176
 
172
- # Set your CachekitIO API key
177
+ # Set your CacheKit Cloud API key
173
178
  # export CACHEKIT_API_KEY="your-api-key" # pragma: allowlist secret
174
179
 
175
- @cache.io() # Uses CachekitIO SaaS backend — no Redis to manage
180
+ @cache.io() # Uses the CacheKit Cloud backend — no Redis to manage
176
181
  def expensive_api_call(user_id: int):
177
182
  return fetch_user_data(user_id)
178
183
  ```
179
184
 
180
- *cachekit.io is in closed beta — [request access](https://cachekit.io) to get started.*
185
+ *CacheKit Cloud is in closed beta: sign-up is by waitlist at [dash.cachekit.io](https://dash.cachekit.io).*
181
186
 
182
- How fresh a cachekit.io read is, and how long a deleted project's data stays readable: [Consistency and Deletion](https://docs.cachekit.io/concepts/consistency/).
187
+ During the beta the backend reports hit counts and L1 hit latency to CacheKit Cloud by default; `CACHEKIT_TELEMETRY=false` turns that off ([Telemetry](docs/backends/cachekitio.md#telemetry)).
188
+
189
+ How fresh a CacheKit Cloud read is, and how long a deleted project's data stays readable: [Consistency and Deletion](https://docs.cachekit.io/concepts/consistency/).
183
190
 
184
191
  </details>
185
192
 
@@ -205,9 +212,9 @@ def expensive_api_call(user_id: int):
205
212
 
206
213
  ---
207
214
 
208
- > **CachekitIO Cloud (Beta)**
215
+ > **CacheKit Cloud (Closed Beta)**
209
216
  > Managed caching with zero infrastructure. L1+L2 caching, circuit breaker, and automatic failover — no Redis to manage.
210
- > *cachekit.io is in closed beta — [request access](https://cachekit.io) to get started.*
217
+ > *CacheKit Cloud is in closed beta: sign-up is by waitlist at [dash.cachekit.io](https://dash.cachekit.io).*
211
218
 
212
219
  ---
213
220
 
@@ -244,12 +251,12 @@ Setting `CACHEKIT_MASTER_KEY` instead of passing `master_key=` means every prese
244
251
  | Integrity Checking | - | ✅ | - | ✅ | ✅ 🔒 |
245
252
  | Encryption | - | - | - | - | ✅ Required |
246
253
  | L1 SWR (L1-only mode) | - | ✅ | - | ✅ | - |
247
- | Prometheus Metrics | - | -¹ | - | ✅ | ✅ |
254
+ | Prometheus Metrics¹ | - | -¹ | - | ✅ | ✅ |
248
255
  | Structured Logging | - | ✅ | - | ✅ | ✅ |
249
256
  | Backend unreachable² | Function runs, no error | Function runs, no error | Function runs, no error | Function runs, no error | Function runs, no error |
250
257
  | **Use Case** | High throughput | Local debugging | Deterministic tests | Production reliability | Compliance/security |
251
258
 
252
- > ¹ `@cache.dev` exports no Prometheus metrics except `circuit_breaker_state`, which is recorded for every function whose circuit breaker is enabled.
259
+ > ¹ A preset with a ✅ records `get` and `set` operations only when it has a backend: in L1-only mode it records none. `@cache.dev` records none of them: it sets `enable_prometheus_metrics=False`. Every preset still records the [series that ignore `MonitoringConfig`](docs/features/prometheus-metrics.md#series-that-ignore-monitoringconfig), such as `circuit_breaker_state`. See [Which Decorators Record](docs/features/prometheus-metrics.md#which-decorators-record).
253
260
  >
254
261
  > ² A backend that is down or unreachable never raises to the caller, on any preset (`@cache.io` included): the call logs the failure and runs your function. What gets cached meanwhile is in [Connection Errors](docs/error-codes.md#connection-errors).
255
262
  >
@@ -261,7 +268,7 @@ Setting `CACHEKIT_MASTER_KEY` instead of passing `master_key=` means every prese
261
268
  >
262
269
  > **L1 SWR** (within-TTL background refresh) runs only in L1-only mode (`backend=None`) — with a backend configured it has no effect. `@cache.io` additionally ships past-TTL SWR via `stale_ttl` ([docs](docs/configuration.md#stale-while-revalidate-stale_ttl)).
263
270
  >
264
- > **`@cache.io()`** mirrors `@cache.production` (full reliability + observability) but routes to the managed CachekitIO SaaS backend instead of Redis. **`@cache.local()`** is a Python-only, SDK-local preset: a separate in-process path backed by `ObjectCache` (raw object references, entry-count LRU, no serialization) — the reliability and encryption features listed above do not apply to it.
271
+ > **`@cache.io()`** mirrors `@cache.production` (full reliability + observability) but routes to CacheKit Cloud instead of Redis. **`@cache.local()`** is a Python-only, SDK-local preset: a separate in-process path backed by `ObjectCache` (raw object references, entry-count LRU, no serialization) — the reliability and encryption features listed above do not apply to it.
265
272
 
266
273
  <details>
267
274
  <summary><strong>Additional Presets: <code>@cache.dev</code> and <code>@cache.test</code></strong></summary>
@@ -269,7 +276,7 @@ Setting `CACHEKIT_MASTER_KEY` instead of passing `master_key=` means every prese
269
276
  `@cache.dev` and `@cache.test` are Python-only, SDK-local presets. See the comparison table above for the exact feature set of each preset.
270
277
 
271
278
  ```python
272
- # Development: verbose logging, integrity checks on, Prometheus off except circuit_breaker_state
279
+ # Development: verbose logging, integrity checks on, no cache-operation Prometheus series
273
280
  @cache.dev
274
281
  def debug_expensive_call():
275
282
  return complex_computation()
@@ -297,7 +304,7 @@ def test_cached_function():
297
304
  │ └─────────────┘ └─────────────┘ └─────────────────────┘ │
298
305
  ├─────────────────────────────────────────────────────────────┤
299
306
  │ L1 Cache (In-Memory) │ L2 Cache (Pluggable Backend) │
300
- │ in-process │ Redis / CachekitIO / File / │
307
+ │ in-process │ Redis / CacheKit Cloud / File / │
301
308
  │ │ Memcached: one backend round trip │
302
309
  ├─────────────────────────────────────────────────────────────┤
303
310
  │ Rust Core (PyO3) │
@@ -315,17 +322,17 @@ def test_cached_function():
315
322
 
316
323
  ## Performance
317
324
 
318
- What a call costs inside your process, from the wall-clock guards in `tests/performance/` run on a GitHub-hosted `ubuntu-latest` runner (AMD EPYC 7763, 4 vCPUs) with CPython 3.14.7. Each figure is the mean of five run medians with its 95% band.
325
+ What a call costs inside your process, from the wall-clock guards in `tests/performance/` run on a GitHub-hosted `ubuntu-latest` runner (AMD EPYC 7763, 4 vCPUs) with CPython 3.14.7. Each figure is the mean of five run medians with its 95% band. The two L1-only rows come from [a later run](https://github.com/cachekit-io/cachekit-py/actions/runs/41210621694) on the same CPU model and Python as the other two.
319
326
 
320
327
  | Path | Time per call |
321
328
  |:-----|--------------:|
322
- | `@cache(backend=None)` L1 hit, 100-user nested dict (23.5 KB as MessagePack) | 0.906 ± 0.007 µs |
323
- | `@cache(backend=None)` L1 hit, 10K-row DataFrame | 0.91 ± 0.01 µs |
324
- | L1 hit with an L2 backend configured, same dict | 310 ± 20 µs |
325
- | Redis L2 hit with L1 disabled, same dict, Redis in a container on the same machine | 551 ± 18 µs |
329
+ | `@cache(backend=None)` L1 hit, 100-user nested dict (23.5 KB as MessagePack) | 0.341 ± 0.001 µs |
330
+ | `@cache(backend=None)` L1 hit, 10K-row DataFrame | 0.34 ± 0.00 µs |
331
+ | L1 hit with an L2 backend configured, same dict | 284 ± 2 µs |
332
+ | Redis L2 hit with L1 disabled, same dict, Redis in a container on the same machine | 539 ± 3 µs |
326
333
 
327
- - With no backend, L1 keeps the returned object itself, whatever the payload's size, and a repeat call with a few short scalar arguments finds its cache key in a per-function memo instead of building it again ([which calls](https://github.com/cachekit-io/cachekit-py/blob/main/docs/performance.md#decorator--l1-hit-hot-path)). Any other call builds its key every time, as does every call to a function with a backend.
328
- - With an L2 backend, L1 holds the serialized bytes and every hit deserializes them: for this dict, about 340 times the L1-only hit. Encryption adds a decrypt to each such hit; on this run that added 10 ± 21 µs, inside the noise.
334
+ - With no backend, L1 keeps the returned object itself, whatever the payload's size, and a repeat call with a few short scalar arguments is answered by one call into the Rust extension that takes no lock ([which calls](https://github.com/cachekit-io/cachekit-py/blob/main/docs/performance.md#decorator--l1-hit-hot-path)). Any other call builds its key every time, as does every call to a function with a backend.
335
+ - With an L2 backend, L1 holds the serialized bytes and every hit deserializes them: for this dict, about 830 times the L1-only hit. This run predates the Rust decode every such hit now runs, which bounds and decodes the MessagePack in one pass at about half the instructions. Encryption adds a decrypt to each such hit; this run measured the difference at 9.3 ± 1.0 µs.
329
336
  - An L2 hit adds the backend's round trip. A Redis on another machine adds its network latency to the last row.
330
337
 
331
338
  Hosted runners come with other CPU models too: in a run of an earlier commit, an EPYC 9V45 runner took about half as long on every row, and other machines give other figures. The [Performance Guide](https://github.com/cachekit-io/cachekit-py/blob/main/docs/performance.md#github-hosted-runner) names the guard behind each figure and how to run it, and the repository's `Bench` workflow repeats the run.
@@ -338,9 +345,9 @@ Hosted runners come with other CPU models too: in a run of an earlier commit, an
338
345
 
339
346
  - Circuit breaker with graceful degradation
340
347
  - Connection pooling with thread affinity
341
- - CachekitIO keeps idle connections pooled until the server closes them, with TCP keepalive probes, so a request after a pause skips a new TLS handshake
342
- - Distributed locking prevents cache stampedes
343
- - Pluggable backend abstraction (Redis, CachekitIO, File, Memcached, custom)
348
+ - The CacheKit Cloud backend keeps idle connections pooled until the server closes them, with TCP keepalive probes, so a request after a pause skips a new TLS handshake
349
+ - Distributed locking guards against cache stampedes across pods, best effort (async functions, on a backend that supports it)
350
+ - Pluggable backend abstraction (Redis, CacheKit Cloud, File, Memcached, custom)
344
351
  - Untrusted-decode bounds: nesting depth and header-declared allocation are capped on every cache read (a forged entry is a bounded cache miss), verified against the protocol's shared [`decode-bounds.json`](https://github.com/cachekit-io/protocol/blob/42ea601f3c1656df0a06904afa2406604f43f57d/test-vectors/decode-bounds.json) vectors
345
352
 
346
353
  > [!NOTE]
@@ -372,7 +379,7 @@ def get_large_dataset(date: str):
372
379
  return pd.read_csv(f"data/{date}.csv")
373
380
  ```
374
381
 
375
- Encrypted DataFrames go through `@cache.secure`, which takes any serializer whose class declares `cross_sdk_compatible = True` ([details](docs/serializers/encryption.md#composability)). A file backend keeps this example self-contained; production uses Redis or cachekit.io:
382
+ Encrypted DataFrames go through `@cache.secure`, which takes any serializer whose class declares `cross_sdk_compatible = True` ([details](docs/serializers/encryption.md#composability)). A file backend keeps this example self-contained; production uses Redis or CacheKit Cloud:
376
383
 
377
384
  ```python
378
385
  import tempfile
@@ -459,7 +466,7 @@ See [SECURITY.md][security-url] for vulnerability reporting and detailed documen
459
466
  ### Monitoring & Observability
460
467
 
461
468
  - **Per-function statistics** - `cache_info()` on every decorated function, modelled on `functools.lru_cache`
462
- - **Prometheus metrics** - Recorded by default (your app owns exposition)
469
+ - **Prometheus metrics** - Recorded by default by a decorator with a backend (your app owns exposition)
463
470
  - **Structured logging** - Context-aware, per-operation fields
464
471
  - **Health checks** - Comprehensive status endpoints
465
472
 
@@ -494,9 +501,9 @@ exposition setup.
494
501
  **Free-threaded CPython (3.15t, 3.14t):** the core suites run green on
495
502
  free-threaded 3.14 and 3.15 with the GIL verified disabled (CI job
496
503
  `test-freethreaded`), and the Rust extension declares free-threaded safety
497
- (`gil_used = false`). **From the release after 0.24.1, 3.15t has a wheel**:
504
+ (`gil_used = false`). **3.15t has a wheel**:
498
505
  the 3.15+ wheel is `abi3t`, so it installs on 3.15t without a Rust toolchain
499
- (pip 26.1+ or uv 0.11.3+). **3.14t is tested
506
+ ([with a recent pip or uv](docs/free-threading.md#cpython-315-one-abi3t-wheel)). **3.14t is tested
500
507
  but has no wheel** (it builds from source). Free-threaded builds are not
501
508
  officially supported yet — blocked upstream on
502
509
  orjson (no free-threaded wheels) and hiredis (re-enables the GIL on import;
@@ -555,7 +562,7 @@ info = expensive_func.cache_info()
555
562
  | [L1 Invalidation & SWR][l1-invalidation-url] | Invalidation scope (incl. cross-process whole-function on tenant-scoped Redis from the environment or `RedisBackendProvider`), opt-in cross-process L1 eviction, stale-while-revalidate |
556
563
  | [Reference Caching][reference-caching-url] | `@cache.local()` for non-serializable objects |
557
564
  | [Rust Serialization][rust-serialization-url] | ByteStorage layer: LZ4, xxHash3, AES-256-GCM |
558
- | [SSRF Protection][ssrf-url] | URL allowlisting for the CachekitIO backend |
565
+ | [SSRF Protection][ssrf-url] | URL allowlisting for the CacheKit Cloud backend |
559
566
  | [Write-Behind][write-behind-url] | Opt-in `write_behind=True`: a miss returns before its L2 write lands |
560
567
 
561
568
  ---
@@ -569,16 +576,17 @@ info = expensive_func.cache_info()
569
576
  # CACHEKIT_MEMCACHED_SERVERS, CACHEKIT_FILE_CACHE_DIR. Two or more is a ConfigurationError at
570
577
  # first call, and decorators relying on env auto-detection run uncached (docs/backends/README.md).
571
578
  # REDIS_URL is only a fallback and never conflicts. With none of them set, decorators cache
572
- # L1-only, in process (encrypted and interop decorators still use localhost Redis).
579
+ # L1-only, in process (encrypted decorators still use localhost Redis; interop decorators refuse
580
+ # any Redis resolved here, which is tenant-prefixed, and need backend=RedisBackend()).
573
581
 
574
582
  # Redis Connection (priority: CACHEKIT_REDIS_URL > REDIS_URL)
575
583
  CACHEKIT_REDIS_URL="redis://localhost:6379" # Primary (preferred)
576
584
  REDIS_URL="redis://localhost:6379" # Fallback
577
585
 
578
- # CachekitIO SaaS Backend (closed beta — request access at cachekit.io)
586
+ # CacheKit Cloud backend (closed beta: join the waitlist at https://dash.cachekit.io)
579
587
  # For @cache.io() next to Redis, pass api_key= from your secret store instead.
580
588
  # CACHEKIT_API_KEY="your-api-key" # pragma: allowlist secret
581
- CACHEKIT_API_URL="https://api.cachekit.io" # Default SaaS endpoint
589
+ CACHEKIT_API_URL="https://api.cachekit.io" # Default CacheKit Cloud endpoint
582
590
 
583
591
  # Memcached Backend (optional: pip install cachekit[memcached])
584
592
  # CACHEKIT_MEMCACHED_SERVERS='["mc1:11211", "mc2:11211"]' # Default: 127.0.0.1:11211
@@ -34,15 +34,17 @@ def expensive_function():
34
34
  return fetch_data()
35
35
  ```
36
36
 
37
- That's it. You get:
37
+ With nothing configured, that plain `@cache` caches in process memory (L1) only: no backend,
38
+ no distributed lock, no encryption. Configure a backend and pick a [preset](#intent-based-optimization),
39
+ and you get:
38
40
 
39
41
  | Feature | Description |
40
42
  |:--------|:------------|
41
43
  | **Circuit breaker** | Prevents cascading failures |
42
- | **Distributed locking** | Multi-pod safety |
44
+ | **Distributed locking** | Multi-pod safety (async functions, on a backend that supports it) |
43
45
  | **Prometheus metrics** | Built-in observability |
44
46
  | **MessagePack serialization** | Efficient with optional compression |
45
- | **Zero-knowledge encryption** | Client-side AES-256-GCM |
47
+ | **Zero-knowledge encryption** | Client-side AES-256-GCM, opt-in (`@cache.secure`) |
46
48
 
47
49
  ---
48
50
 
@@ -84,7 +86,7 @@ backend that fits your infrastructure — they're peers behind the same `@cache`
84
86
  | Backend | Best for | Select with |
85
87
  |---------|----------|-------------|
86
88
  | Redis | Self-hosted, full control | `REDIS_URL` / `CACHEKIT_REDIS_URL` |
87
- | CachekitIO | Managed, zero-ops (beta) | `CACHEKIT_API_KEY` |
89
+ | CacheKit Cloud | Managed, zero-ops (closed beta) | `CACHEKIT_API_KEY` |
88
90
  | Memcached | High-throughput, existing infra | `CACHEKIT_MEMCACHED_SERVERS` |
89
91
  | File / L1-only | Local dev, tests, no external deps | `CACHEKIT_FILE_CACHE_DIR` / nothing (or `backend=None`) |
90
92
 
@@ -102,31 +104,36 @@ def expensive_api_call(user_id: int):
102
104
  ```
103
105
 
104
106
  > [!WARNING]
105
- > Earlier releases connected to `redis://localhost:6379` when nothing was configured. To keep
106
- > that Redis, set `REDIS_URL=redis://localhost:6379` (or `CACHEKIT_REDIS_URL`). `@cache.secure`,
107
- > `encryption=True` and `interop=` still need a backend and still fall back to localhost Redis;
108
- > see [Backend Resolution Priority](docs/backends/README.md#backend-resolution-priority).
107
+ > With nothing configured, `@cache` never connects to `redis://localhost:6379`. To use that
108
+ > Redis, set `REDIS_URL=redis://localhost:6379` (or `CACHEKIT_REDIS_URL`); upgraders, see the
109
+ > [CHANGELOG](CHANGELOG.md). `@cache.secure`
110
+ > and `encryption=True` still need a backend and still fall back to localhost Redis; see
111
+ > [Backend Resolution Priority](docs/backends/README.md#backend-resolution-priority). `interop=`
112
+ > refuses that Redis, and any Redis resolved without `backend=`, because it prefixes keys with the
113
+ > tenant: pass `backend=RedisBackend()` ([Interop Mode](docs/features/interop-mode.md)).
109
114
 
110
115
  ### More Backends
111
116
 
112
117
  <details>
113
- <summary><strong>CachekitIO — Managed SaaS (Beta)</strong></summary>
118
+ <summary><strong>CacheKit Cloud — Managed (Closed Beta)</strong></summary>
114
119
 
115
120
  ```python notest
116
121
  import os
117
122
  from cachekit import cache
118
123
 
119
- # Set your CachekitIO API key
124
+ # Set your CacheKit Cloud API key
120
125
  # export CACHEKIT_API_KEY="your-api-key" # pragma: allowlist secret
121
126
 
122
- @cache.io() # Uses CachekitIO SaaS backend — no Redis to manage
127
+ @cache.io() # Uses the CacheKit Cloud backend — no Redis to manage
123
128
  def expensive_api_call(user_id: int):
124
129
  return fetch_user_data(user_id)
125
130
  ```
126
131
 
127
- *cachekit.io is in closed beta — [request access](https://cachekit.io) to get started.*
132
+ *CacheKit Cloud is in closed beta: sign-up is by waitlist at [dash.cachekit.io](https://dash.cachekit.io).*
128
133
 
129
- How fresh a cachekit.io read is, and how long a deleted project's data stays readable: [Consistency and Deletion](https://docs.cachekit.io/concepts/consistency/).
134
+ During the beta the backend reports hit counts and L1 hit latency to CacheKit Cloud by default; `CACHEKIT_TELEMETRY=false` turns that off ([Telemetry](docs/backends/cachekitio.md#telemetry)).
135
+
136
+ How fresh a CacheKit Cloud read is, and how long a deleted project's data stays readable: [Consistency and Deletion](https://docs.cachekit.io/concepts/consistency/).
130
137
 
131
138
  </details>
132
139
 
@@ -152,9 +159,9 @@ def expensive_api_call(user_id: int):
152
159
 
153
160
  ---
154
161
 
155
- > **CachekitIO Cloud (Beta)**
162
+ > **CacheKit Cloud (Closed Beta)**
156
163
  > Managed caching with zero infrastructure. L1+L2 caching, circuit breaker, and automatic failover — no Redis to manage.
157
- > *cachekit.io is in closed beta — [request access](https://cachekit.io) to get started.*
164
+ > *CacheKit Cloud is in closed beta: sign-up is by waitlist at [dash.cachekit.io](https://dash.cachekit.io).*
158
165
 
159
166
  ---
160
167
 
@@ -191,12 +198,12 @@ Setting `CACHEKIT_MASTER_KEY` instead of passing `master_key=` means every prese
191
198
  | Integrity Checking | - | ✅ | - | ✅ | ✅ 🔒 |
192
199
  | Encryption | - | - | - | - | ✅ Required |
193
200
  | L1 SWR (L1-only mode) | - | ✅ | - | ✅ | - |
194
- | Prometheus Metrics | - | -¹ | - | ✅ | ✅ |
201
+ | Prometheus Metrics¹ | - | -¹ | - | ✅ | ✅ |
195
202
  | Structured Logging | - | ✅ | - | ✅ | ✅ |
196
203
  | Backend unreachable² | Function runs, no error | Function runs, no error | Function runs, no error | Function runs, no error | Function runs, no error |
197
204
  | **Use Case** | High throughput | Local debugging | Deterministic tests | Production reliability | Compliance/security |
198
205
 
199
- > ¹ `@cache.dev` exports no Prometheus metrics except `circuit_breaker_state`, which is recorded for every function whose circuit breaker is enabled.
206
+ > ¹ A preset with a ✅ records `get` and `set` operations only when it has a backend: in L1-only mode it records none. `@cache.dev` records none of them: it sets `enable_prometheus_metrics=False`. Every preset still records the [series that ignore `MonitoringConfig`](docs/features/prometheus-metrics.md#series-that-ignore-monitoringconfig), such as `circuit_breaker_state`. See [Which Decorators Record](docs/features/prometheus-metrics.md#which-decorators-record).
200
207
  >
201
208
  > ² A backend that is down or unreachable never raises to the caller, on any preset (`@cache.io` included): the call logs the failure and runs your function. What gets cached meanwhile is in [Connection Errors](docs/error-codes.md#connection-errors).
202
209
  >
@@ -208,7 +215,7 @@ Setting `CACHEKIT_MASTER_KEY` instead of passing `master_key=` means every prese
208
215
  >
209
216
  > **L1 SWR** (within-TTL background refresh) runs only in L1-only mode (`backend=None`) — with a backend configured it has no effect. `@cache.io` additionally ships past-TTL SWR via `stale_ttl` ([docs](docs/configuration.md#stale-while-revalidate-stale_ttl)).
210
217
  >
211
- > **`@cache.io()`** mirrors `@cache.production` (full reliability + observability) but routes to the managed CachekitIO SaaS backend instead of Redis. **`@cache.local()`** is a Python-only, SDK-local preset: a separate in-process path backed by `ObjectCache` (raw object references, entry-count LRU, no serialization) — the reliability and encryption features listed above do not apply to it.
218
+ > **`@cache.io()`** mirrors `@cache.production` (full reliability + observability) but routes to CacheKit Cloud instead of Redis. **`@cache.local()`** is a Python-only, SDK-local preset: a separate in-process path backed by `ObjectCache` (raw object references, entry-count LRU, no serialization) — the reliability and encryption features listed above do not apply to it.
212
219
 
213
220
  <details>
214
221
  <summary><strong>Additional Presets: <code>@cache.dev</code> and <code>@cache.test</code></strong></summary>
@@ -216,7 +223,7 @@ Setting `CACHEKIT_MASTER_KEY` instead of passing `master_key=` means every prese
216
223
  `@cache.dev` and `@cache.test` are Python-only, SDK-local presets. See the comparison table above for the exact feature set of each preset.
217
224
 
218
225
  ```python
219
- # Development: verbose logging, integrity checks on, Prometheus off except circuit_breaker_state
226
+ # Development: verbose logging, integrity checks on, no cache-operation Prometheus series
220
227
  @cache.dev
221
228
  def debug_expensive_call():
222
229
  return complex_computation()
@@ -244,7 +251,7 @@ def test_cached_function():
244
251
  │ └─────────────┘ └─────────────┘ └─────────────────────┘ │
245
252
  ├─────────────────────────────────────────────────────────────┤
246
253
  │ L1 Cache (In-Memory) │ L2 Cache (Pluggable Backend) │
247
- │ in-process │ Redis / CachekitIO / File / │
254
+ │ in-process │ Redis / CacheKit Cloud / File / │
248
255
  │ │ Memcached: one backend round trip │
249
256
  ├─────────────────────────────────────────────────────────────┤
250
257
  │ Rust Core (PyO3) │
@@ -262,17 +269,17 @@ def test_cached_function():
262
269
 
263
270
  ## Performance
264
271
 
265
- What a call costs inside your process, from the wall-clock guards in `tests/performance/` run on a GitHub-hosted `ubuntu-latest` runner (AMD EPYC 7763, 4 vCPUs) with CPython 3.14.7. Each figure is the mean of five run medians with its 95% band.
272
+ What a call costs inside your process, from the wall-clock guards in `tests/performance/` run on a GitHub-hosted `ubuntu-latest` runner (AMD EPYC 7763, 4 vCPUs) with CPython 3.14.7. Each figure is the mean of five run medians with its 95% band. The two L1-only rows come from [a later run](https://github.com/cachekit-io/cachekit-py/actions/runs/41210621694) on the same CPU model and Python as the other two.
266
273
 
267
274
  | Path | Time per call |
268
275
  |:-----|--------------:|
269
- | `@cache(backend=None)` L1 hit, 100-user nested dict (23.5 KB as MessagePack) | 0.906 ± 0.007 µs |
270
- | `@cache(backend=None)` L1 hit, 10K-row DataFrame | 0.91 ± 0.01 µs |
271
- | L1 hit with an L2 backend configured, same dict | 310 ± 20 µs |
272
- | Redis L2 hit with L1 disabled, same dict, Redis in a container on the same machine | 551 ± 18 µs |
276
+ | `@cache(backend=None)` L1 hit, 100-user nested dict (23.5 KB as MessagePack) | 0.341 ± 0.001 µs |
277
+ | `@cache(backend=None)` L1 hit, 10K-row DataFrame | 0.34 ± 0.00 µs |
278
+ | L1 hit with an L2 backend configured, same dict | 284 ± 2 µs |
279
+ | Redis L2 hit with L1 disabled, same dict, Redis in a container on the same machine | 539 ± 3 µs |
273
280
 
274
- - With no backend, L1 keeps the returned object itself, whatever the payload's size, and a repeat call with a few short scalar arguments finds its cache key in a per-function memo instead of building it again ([which calls](https://github.com/cachekit-io/cachekit-py/blob/main/docs/performance.md#decorator--l1-hit-hot-path)). Any other call builds its key every time, as does every call to a function with a backend.
275
- - With an L2 backend, L1 holds the serialized bytes and every hit deserializes them: for this dict, about 340 times the L1-only hit. Encryption adds a decrypt to each such hit; on this run that added 10 ± 21 µs, inside the noise.
281
+ - With no backend, L1 keeps the returned object itself, whatever the payload's size, and a repeat call with a few short scalar arguments is answered by one call into the Rust extension that takes no lock ([which calls](https://github.com/cachekit-io/cachekit-py/blob/main/docs/performance.md#decorator--l1-hit-hot-path)). Any other call builds its key every time, as does every call to a function with a backend.
282
+ - With an L2 backend, L1 holds the serialized bytes and every hit deserializes them: for this dict, about 830 times the L1-only hit. This run predates the Rust decode every such hit now runs, which bounds and decodes the MessagePack in one pass at about half the instructions. Encryption adds a decrypt to each such hit; this run measured the difference at 9.3 ± 1.0 µs.
276
283
  - An L2 hit adds the backend's round trip. A Redis on another machine adds its network latency to the last row.
277
284
 
278
285
  Hosted runners come with other CPU models too: in a run of an earlier commit, an EPYC 9V45 runner took about half as long on every row, and other machines give other figures. The [Performance Guide](https://github.com/cachekit-io/cachekit-py/blob/main/docs/performance.md#github-hosted-runner) names the guard behind each figure and how to run it, and the repository's `Bench` workflow repeats the run.
@@ -285,9 +292,9 @@ Hosted runners come with other CPU models too: in a run of an earlier commit, an
285
292
 
286
293
  - Circuit breaker with graceful degradation
287
294
  - Connection pooling with thread affinity
288
- - CachekitIO keeps idle connections pooled until the server closes them, with TCP keepalive probes, so a request after a pause skips a new TLS handshake
289
- - Distributed locking prevents cache stampedes
290
- - Pluggable backend abstraction (Redis, CachekitIO, File, Memcached, custom)
295
+ - The CacheKit Cloud backend keeps idle connections pooled until the server closes them, with TCP keepalive probes, so a request after a pause skips a new TLS handshake
296
+ - Distributed locking guards against cache stampedes across pods, best effort (async functions, on a backend that supports it)
297
+ - Pluggable backend abstraction (Redis, CacheKit Cloud, File, Memcached, custom)
291
298
  - Untrusted-decode bounds: nesting depth and header-declared allocation are capped on every cache read (a forged entry is a bounded cache miss), verified against the protocol's shared [`decode-bounds.json`](https://github.com/cachekit-io/protocol/blob/42ea601f3c1656df0a06904afa2406604f43f57d/test-vectors/decode-bounds.json) vectors
292
299
 
293
300
  > [!NOTE]
@@ -319,7 +326,7 @@ def get_large_dataset(date: str):
319
326
  return pd.read_csv(f"data/{date}.csv")
320
327
  ```
321
328
 
322
- Encrypted DataFrames go through `@cache.secure`, which takes any serializer whose class declares `cross_sdk_compatible = True` ([details](docs/serializers/encryption.md#composability)). A file backend keeps this example self-contained; production uses Redis or cachekit.io:
329
+ Encrypted DataFrames go through `@cache.secure`, which takes any serializer whose class declares `cross_sdk_compatible = True` ([details](docs/serializers/encryption.md#composability)). A file backend keeps this example self-contained; production uses Redis or CacheKit Cloud:
323
330
 
324
331
  ```python
325
332
  import tempfile
@@ -406,7 +413,7 @@ See [SECURITY.md][security-url] for vulnerability reporting and detailed documen
406
413
  ### Monitoring & Observability
407
414
 
408
415
  - **Per-function statistics** - `cache_info()` on every decorated function, modelled on `functools.lru_cache`
409
- - **Prometheus metrics** - Recorded by default (your app owns exposition)
416
+ - **Prometheus metrics** - Recorded by default by a decorator with a backend (your app owns exposition)
410
417
  - **Structured logging** - Context-aware, per-operation fields
411
418
  - **Health checks** - Comprehensive status endpoints
412
419
 
@@ -441,9 +448,9 @@ exposition setup.
441
448
  **Free-threaded CPython (3.15t, 3.14t):** the core suites run green on
442
449
  free-threaded 3.14 and 3.15 with the GIL verified disabled (CI job
443
450
  `test-freethreaded`), and the Rust extension declares free-threaded safety
444
- (`gil_used = false`). **From the release after 0.24.1, 3.15t has a wheel**:
451
+ (`gil_used = false`). **3.15t has a wheel**:
445
452
  the 3.15+ wheel is `abi3t`, so it installs on 3.15t without a Rust toolchain
446
- (pip 26.1+ or uv 0.11.3+). **3.14t is tested
453
+ ([with a recent pip or uv](docs/free-threading.md#cpython-315-one-abi3t-wheel)). **3.14t is tested
447
454
  but has no wheel** (it builds from source). Free-threaded builds are not
448
455
  officially supported yet — blocked upstream on
449
456
  orjson (no free-threaded wheels) and hiredis (re-enables the GIL on import;
@@ -502,7 +509,7 @@ info = expensive_func.cache_info()
502
509
  | [L1 Invalidation & SWR][l1-invalidation-url] | Invalidation scope (incl. cross-process whole-function on tenant-scoped Redis from the environment or `RedisBackendProvider`), opt-in cross-process L1 eviction, stale-while-revalidate |
503
510
  | [Reference Caching][reference-caching-url] | `@cache.local()` for non-serializable objects |
504
511
  | [Rust Serialization][rust-serialization-url] | ByteStorage layer: LZ4, xxHash3, AES-256-GCM |
505
- | [SSRF Protection][ssrf-url] | URL allowlisting for the CachekitIO backend |
512
+ | [SSRF Protection][ssrf-url] | URL allowlisting for the CacheKit Cloud backend |
506
513
  | [Write-Behind][write-behind-url] | Opt-in `write_behind=True`: a miss returns before its L2 write lands |
507
514
 
508
515
  ---
@@ -516,16 +523,17 @@ info = expensive_func.cache_info()
516
523
  # CACHEKIT_MEMCACHED_SERVERS, CACHEKIT_FILE_CACHE_DIR. Two or more is a ConfigurationError at
517
524
  # first call, and decorators relying on env auto-detection run uncached (docs/backends/README.md).
518
525
  # REDIS_URL is only a fallback and never conflicts. With none of them set, decorators cache
519
- # L1-only, in process (encrypted and interop decorators still use localhost Redis).
526
+ # L1-only, in process (encrypted decorators still use localhost Redis; interop decorators refuse
527
+ # any Redis resolved here, which is tenant-prefixed, and need backend=RedisBackend()).
520
528
 
521
529
  # Redis Connection (priority: CACHEKIT_REDIS_URL > REDIS_URL)
522
530
  CACHEKIT_REDIS_URL="redis://localhost:6379" # Primary (preferred)
523
531
  REDIS_URL="redis://localhost:6379" # Fallback
524
532
 
525
- # CachekitIO SaaS Backend (closed beta — request access at cachekit.io)
533
+ # CacheKit Cloud backend (closed beta: join the waitlist at https://dash.cachekit.io)
526
534
  # For @cache.io() next to Redis, pass api_key= from your secret store instead.
527
535
  # CACHEKIT_API_KEY="your-api-key" # pragma: allowlist secret
528
- CACHEKIT_API_URL="https://api.cachekit.io" # Default SaaS endpoint
536
+ CACHEKIT_API_URL="https://api.cachekit.io" # Default CacheKit Cloud endpoint
529
537
 
530
538
  # Memcached Backend (optional: pip install cachekit[memcached])
531
539
  # CACHEKIT_MEMCACHED_SERVERS='["mc1:11211", "mc2:11211"]' # Default: 127.0.0.1:11211
@@ -6,7 +6,7 @@ build-backend = "maturin"
6
6
 
7
7
  [project]
8
8
  name = "cachekit"
9
- version = "0.25.1"
9
+ version = "0.26.0"
10
10
  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."
11
11
  readme = "README.md"
12
12
  license = {text = "MIT"}
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "cachekit-rs"
3
- version = "0.25.1"
3
+ version = "0.26.0"
4
4
  edition = "2021"
5
5
  authors = ["cachekit Contributors"]
6
6
  description = "High-performance storage engine for caching with compression and encryption"