cachekit 0.20.0__cp314-cp314-win_amd64.whl
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.
- LICENSE +21 -0
- README.md +539 -0
- cachekit/__init__.py +136 -0
- cachekit/_rust_serializer.cp314-win_amd64.pyd +0 -0
- cachekit/backends/__init__.py +114 -0
- cachekit/backends/base.py +450 -0
- cachekit/backends/base_config.py +104 -0
- cachekit/backends/cachekitio/__init__.py +12 -0
- cachekit/backends/cachekitio/backend.py +911 -0
- cachekit/backends/cachekitio/client.py +187 -0
- cachekit/backends/cachekitio/config.py +225 -0
- cachekit/backends/cachekitio/error_handler.py +134 -0
- cachekit/backends/cachekitio/session.py +59 -0
- cachekit/backends/errors.py +198 -0
- cachekit/backends/file/__init__.py +30 -0
- cachekit/backends/file/backend.py +1159 -0
- cachekit/backends/file/config.py +191 -0
- cachekit/backends/memcached/__init__.py +25 -0
- cachekit/backends/memcached/backend.py +255 -0
- cachekit/backends/memcached/config.py +137 -0
- cachekit/backends/memcached/error_handler.py +98 -0
- cachekit/backends/provider.py +255 -0
- cachekit/backends/redis/__init__.py +12 -0
- cachekit/backends/redis/backend.py +285 -0
- cachekit/backends/redis/client.py +263 -0
- cachekit/backends/redis/config.py +117 -0
- cachekit/backends/redis/error_handler.py +185 -0
- cachekit/backends/redis/provider.py +804 -0
- cachekit/cache_handler.py +2301 -0
- cachekit/config/__init__.py +48 -0
- cachekit/config/decorator.py +603 -0
- cachekit/config/nested.py +393 -0
- cachekit/config/settings.py +382 -0
- cachekit/config/singleton.py +100 -0
- cachekit/config/validation.py +243 -0
- cachekit/decorators/__init__.py +15 -0
- cachekit/decorators/intent.py +283 -0
- cachekit/decorators/local_wrapper.py +145 -0
- cachekit/decorators/main.py +7 -0
- cachekit/decorators/orchestrator.py +419 -0
- cachekit/decorators/session.py +141 -0
- cachekit/decorators/stats_context.py +64 -0
- cachekit/decorators/tenant_context.py +256 -0
- cachekit/decorators/utils/__init__.py +3 -0
- cachekit/decorators/wrapper.py +2490 -0
- cachekit/di.py +55 -0
- cachekit/hash_utils.py +145 -0
- cachekit/health.py +647 -0
- cachekit/hiredis_compat.py +98 -0
- cachekit/imports.py +48 -0
- cachekit/interop.py +518 -0
- cachekit/key_generator.py +450 -0
- cachekit/l1_cache.py +642 -0
- cachekit/logging.py +386 -0
- cachekit/monitoring/__init__.py +8 -0
- cachekit/monitoring/pool_monitor.py +346 -0
- cachekit/monitoring/protocols.py +50 -0
- cachekit/object_cache.py +471 -0
- cachekit/py.typed +0 -0
- cachekit/reliability/__init__.py +36 -0
- cachekit/reliability/async_metrics.py +804 -0
- cachekit/reliability/circuit_breaker.py +459 -0
- cachekit/reliability/error_classification.py +91 -0
- cachekit/reliability/load_control.py +214 -0
- cachekit/reliability/metrics_collection.py +407 -0
- cachekit/reliability/profiles.py +236 -0
- cachekit/serializers/__init__.py +260 -0
- cachekit/serializers/arrow_serializer.py +450 -0
- cachekit/serializers/auto_serializer.py +1262 -0
- cachekit/serializers/base.py +480 -0
- cachekit/serializers/encryption_wrapper.py +885 -0
- cachekit/serializers/interop_serializer.py +64 -0
- cachekit/serializers/orjson_serializer.py +212 -0
- cachekit/serializers/standard_serializer.py +411 -0
- cachekit/serializers/wrapper.py +169 -0
- cachekit-0.20.0.dist-info/METADATA +596 -0
- cachekit-0.20.0.dist-info/RECORD +80 -0
- cachekit-0.20.0.dist-info/WHEEL +4 -0
- cachekit-0.20.0.dist-info/licenses/LICENSE +21 -0
- cachekit-0.20.0.dist-info/sboms/cachekit-rs.cyclonedx.json +3546 -0
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 cachekit Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
README.md
ADDED
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# cachekit
|
|
4
|
+
|
|
5
|
+
> **Python caching, batteries included**
|
|
6
|
+
|
|
7
|
+
Backend-agnostic caching with intent-based decorators — circuit breaker, distributed locking, Prometheus metrics, and optional zero-knowledge encryption on a Rust-powered core. Zero config to start, any backend when you scale.
|
|
8
|
+
|
|
9
|
+
[![PyPI Version][pypi-badge]][pypi-url]
|
|
10
|
+
[![Python Versions][python-badge]][pypi-url]
|
|
11
|
+
[![codecov][codecov-badge]][codecov-url]
|
|
12
|
+
[![License: MIT][license-badge]][license-url]
|
|
13
|
+
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
> [!NOTE]
|
|
19
|
+
> **Status: beta** — CacheKit is in closed beta ahead of 1.0. APIs are stabilising; minor breaking changes may still occur between 0.x releases.
|
|
20
|
+
>
|
|
21
|
+
> 🐛 **Found a bug or a rough edge?** Please [open an issue][issues-url] — your feedback directly shapes the path to 1.0.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Why cachekit?
|
|
26
|
+
|
|
27
|
+
**Simple to use, works out of the box.**
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from cachekit import cache
|
|
31
|
+
|
|
32
|
+
@cache
|
|
33
|
+
def expensive_function():
|
|
34
|
+
return fetch_data()
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
That's it. You get:
|
|
38
|
+
|
|
39
|
+
| Feature | Description |
|
|
40
|
+
|:--------|:------------|
|
|
41
|
+
| **Circuit breaker** | Prevents cascading failures |
|
|
42
|
+
| **Distributed locking** | Multi-pod safety |
|
|
43
|
+
| **Prometheus metrics** | Built-in observability |
|
|
44
|
+
| **MessagePack serialization** | Efficient with optional compression |
|
|
45
|
+
| **Zero-knowledge encryption** | Client-side AES-256-GCM |
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Quick Start
|
|
50
|
+
|
|
51
|
+
### Installation
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install cachekit
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Or with [uv][uv-url] (recommended):
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
uv add cachekit
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Setup (choose a backend)
|
|
64
|
+
|
|
65
|
+
cachekit exposes one decorator API over a pluggable backend abstraction. Pick the
|
|
66
|
+
backend that fits your infrastructure — they're peers behind the same `@cache` API:
|
|
67
|
+
|
|
68
|
+
| Backend | Best for | Select with |
|
|
69
|
+
|---------|----------|-------------|
|
|
70
|
+
| Redis | Self-hosted, full control | `REDIS_URL` / `CACHEKIT_REDIS_URL` |
|
|
71
|
+
| CachekitIO | Managed, zero-ops (beta) | `CACHEKIT_API_KEY` |
|
|
72
|
+
| Memcached | High-throughput, existing infra | `CACHEKIT_MEMCACHED_SERVERS` |
|
|
73
|
+
| File / L1-only | Local dev, tests, no external deps | `CACHEKIT_FILE_CACHE_DIR` / `backend=None` |
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
# Run Redis locally or use your existing infrastructure
|
|
77
|
+
export REDIS_URL="redis://localhost:6379"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from cachekit import cache
|
|
82
|
+
|
|
83
|
+
@cache # Auto-detects backend (defaults to Redis at localhost)
|
|
84
|
+
def expensive_api_call(user_id: int):
|
|
85
|
+
return fetch_user_data(user_id)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
> [!TIP]
|
|
89
|
+
> No Redis? No worries! Use `@cache(backend=None)` for L1-only in-memory caching, like `lru_cache`, but with all the bells and whistles.
|
|
90
|
+
|
|
91
|
+
### More Backends
|
|
92
|
+
|
|
93
|
+
<details>
|
|
94
|
+
<summary><strong>CachekitIO — Managed SaaS (Beta)</strong></summary>
|
|
95
|
+
|
|
96
|
+
```python notest
|
|
97
|
+
import os
|
|
98
|
+
from cachekit import cache
|
|
99
|
+
|
|
100
|
+
# Set your CachekitIO API key
|
|
101
|
+
# export CACHEKIT_API_KEY="your-api-key" # pragma: allowlist secret
|
|
102
|
+
|
|
103
|
+
@cache.io() # Uses CachekitIO SaaS backend — no Redis to manage
|
|
104
|
+
def expensive_api_call(user_id: int):
|
|
105
|
+
return fetch_user_data(user_id)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
*cachekit.io is in closed beta — [request access](https://cachekit.io) to get started.*
|
|
109
|
+
|
|
110
|
+
</details>
|
|
111
|
+
|
|
112
|
+
<details>
|
|
113
|
+
<summary><strong>Memcached — Optional</strong></summary>
|
|
114
|
+
|
|
115
|
+
```python notest
|
|
116
|
+
from cachekit import cache
|
|
117
|
+
from cachekit.backends.memcached import MemcachedBackend
|
|
118
|
+
|
|
119
|
+
# pip install cachekit[memcached]
|
|
120
|
+
|
|
121
|
+
backend = MemcachedBackend() # Defaults to 127.0.0.1:11211
|
|
122
|
+
|
|
123
|
+
@cache(backend=backend)
|
|
124
|
+
def expensive_api_call(user_id: int):
|
|
125
|
+
return fetch_user_data(user_id)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
*Requires: `pip install cachekit[memcached]` or `uv add cachekit[memcached]`*
|
|
129
|
+
|
|
130
|
+
</details>
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
> **CachekitIO Cloud (Beta)**
|
|
135
|
+
> Managed caching with zero infrastructure. L1+L2 caching, circuit breaker, and automatic failover — no Redis to manage.
|
|
136
|
+
> *cachekit.io is in closed beta — [request access](https://cachekit.io) to get started.*
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Intent-Based Optimization
|
|
141
|
+
|
|
142
|
+
cachekit provides **preset configurations** for different use cases:
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
# Speed-critical: trading, gaming, real-time
|
|
146
|
+
@cache.minimal
|
|
147
|
+
def get_price(symbol: str):
|
|
148
|
+
return fetch_price(symbol)
|
|
149
|
+
|
|
150
|
+
# Reliability-critical: payments, APIs
|
|
151
|
+
@cache.production
|
|
152
|
+
def process_payment(amount):
|
|
153
|
+
return payment_gateway.charge(amount)
|
|
154
|
+
|
|
155
|
+
# Security-critical: PII, medical, financial (needs a key: master_key= or CACHEKIT_MASTER_KEY)
|
|
156
|
+
@cache.secure(master_key=secret_key)
|
|
157
|
+
def get_user_profile(user_id: int):
|
|
158
|
+
return db.fetch_user(user_id)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
| Feature | `@cache.minimal` | `@cache.dev` | `@cache.test` | `@cache.production` | `@cache.secure` |
|
|
162
|
+
|:--------|:----------------:|:------------:|:-------------:|:-------------------:|:---------------:|
|
|
163
|
+
| Default TTL | 300 s | 300 s | 300 s | 600 s | 600 s |
|
|
164
|
+
| Circuit Breaker | - | ✅ | - | ✅ | ✅ |
|
|
165
|
+
| Backpressure | ✅ | ✅ | - | ✅ | ✅ |
|
|
166
|
+
| Integrity Checking | - | ✅ | - | ✅ | ✅ 🔒 |
|
|
167
|
+
| Encryption | - | - | - | - | ✅ Required |
|
|
168
|
+
| L1 SWR (L1-only mode) | - | ✅ | - | ✅ | - |
|
|
169
|
+
| L1 Invalidation | - | - | - | ✅ | ✅ |
|
|
170
|
+
| Prometheus Metrics | - | - | - | ✅ | ✅ |
|
|
171
|
+
| Tracing | - | ✅ | - | ✅ | ✅ |
|
|
172
|
+
| Structured Logging | - | ✅ | - | ✅ | ✅ |
|
|
173
|
+
| **Use Case** | High throughput | Local debugging | Deterministic tests | Production reliability | Compliance/security |
|
|
174
|
+
|
|
175
|
+
> 🔒 `@cache.secure` forces `integrity_checking=True` — passing `integrity_checking=False` raises `ConfigurationError` at decoration, including as an override next to `config=DecoratorConfig.secure(...)`. `@cache.secure` also rejects `config=`; the RORO form is `@cache(config=DecoratorConfig.secure(...))`.
|
|
176
|
+
>
|
|
177
|
+
> **Default TTL** follows the cross-SDK [intent-preset spec](https://github.com/cachekit-io/protocol/blob/main/spec/intent-presets.md#default-ttl) (`@cache.io` 3600 s) — the same numbers as cachekit-rs and cachekit-ts. `ttl=` overrides it; `ttl=None` is the explicit never-expire opt-in ([details](docs/configuration.md#intent-presets)).
|
|
178
|
+
>
|
|
179
|
+
> **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)).
|
|
180
|
+
>
|
|
181
|
+
> **`@cache.io()`** mirrors `@cache.production` (full reliability + observability) but routes to the managed CachekitIO SaaS backend instead of Redis. **`@cache.local()`** is 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.
|
|
182
|
+
|
|
183
|
+
<details>
|
|
184
|
+
<summary><strong>Additional Presets: <code>@cache.dev</code> and <code>@cache.test</code></strong></summary>
|
|
185
|
+
|
|
186
|
+
See the comparison table above for the exact feature set of each preset.
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
# Development: verbose logging, integrity checks on, Prometheus off
|
|
190
|
+
@cache.dev
|
|
191
|
+
def debug_expensive_call():
|
|
192
|
+
return complex_computation()
|
|
193
|
+
|
|
194
|
+
# Testing: deterministic, all protections off (no circuit breaker, no backpressure)
|
|
195
|
+
@cache.test
|
|
196
|
+
def test_cached_function():
|
|
197
|
+
return fixed_test_value()
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
</details>
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Architecture
|
|
205
|
+
|
|
206
|
+
```
|
|
207
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
208
|
+
│ Application │
|
|
209
|
+
├─────────────────────────────────────────────────────────────┤
|
|
210
|
+
│ @cache Decorator │
|
|
211
|
+
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
|
|
212
|
+
│ │ Circuit │ │ Adaptive │ │ Distributed │ │
|
|
213
|
+
│ │ Breaker │ │ Timeouts │ │ Locking │ │
|
|
214
|
+
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
|
|
215
|
+
├─────────────────────────────────────────────────────────────┤
|
|
216
|
+
│ L1 Cache (In-Memory) │ L2 Cache (Pluggable Backend) │
|
|
217
|
+
│ ~50ns │ Redis / CachekitIO / File / │
|
|
218
|
+
│ │ Memcached ~2-50ms │
|
|
219
|
+
├─────────────────────────────────────────────────────────────┤
|
|
220
|
+
│ Rust Core (PyO3) │
|
|
221
|
+
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
|
|
222
|
+
│ │ LZ4 │ │ xxHash3 │ │ AES-256-GCM │ │
|
|
223
|
+
│ │ Compression │ │ Checksums │ │ Encryption │ │
|
|
224
|
+
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
|
|
225
|
+
└─────────────────────────────────────────────────────────────┘
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
> [!TIP]
|
|
229
|
+
> **Building in Rust?** The core compression, checksums, and encryption are available as a standalone crate: [`cachekit-core`](https://crates.io/crates/cachekit-core) [](https://crates.io/crates/cachekit-core)
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Features
|
|
234
|
+
|
|
235
|
+
### Production Hardened
|
|
236
|
+
|
|
237
|
+
- Circuit breaker with graceful degradation
|
|
238
|
+
- Connection pooling with thread affinity (+28% throughput)
|
|
239
|
+
- Distributed locking prevents cache stampedes
|
|
240
|
+
- Pluggable backend abstraction (Redis, CachekitIO, File, Memcached, custom)
|
|
241
|
+
- 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/2736a81f2f853cf08c5563c0fe7c8361331fa3ad/test-vectors/decode-bounds.json) vectors
|
|
242
|
+
|
|
243
|
+
> [!NOTE]
|
|
244
|
+
> All reliability features are **enabled by default** with `@cache.production`. Use `@cache.minimal` to disable them for maximum throughput.
|
|
245
|
+
|
|
246
|
+
### Smart Serialization
|
|
247
|
+
|
|
248
|
+
| Serializer | Speed | Use Case |
|
|
249
|
+
|:-----------|:-----:|:---------|
|
|
250
|
+
| **StandardSerializer** | ★★★★☆ | General Python types, NumPy, Pandas |
|
|
251
|
+
| **OrjsonSerializer** | ★★★★★ | JSON APIs (2-5x faster than stdlib) — requires `cachekit[json]` |
|
|
252
|
+
| **ArrowSerializer** | ★★★★★ | Large DataFrames (6-23x faster for 10K+ rows) |
|
|
253
|
+
| **EncryptionWrapper** | ★★★★☆ | Wraps any serializer with AES-256-GCM |
|
|
254
|
+
|
|
255
|
+
<details>
|
|
256
|
+
<summary><strong>Serializer Examples</strong></summary>
|
|
257
|
+
|
|
258
|
+
```python
|
|
259
|
+
from cachekit.serializers import OrjsonSerializer, ArrowSerializer
|
|
260
|
+
|
|
261
|
+
# Fast JSON for API responses
|
|
262
|
+
@cache.production(serializer=OrjsonSerializer())
|
|
263
|
+
def get_api_response(endpoint: str):
|
|
264
|
+
return {"status": "success", "data": fetch_api(endpoint)}
|
|
265
|
+
|
|
266
|
+
# Zero-copy DataFrames for large datasets
|
|
267
|
+
@cache(serializer=ArrowSerializer())
|
|
268
|
+
def get_large_dataset(date: str):
|
|
269
|
+
return pd.read_csv(f"data/{date}.csv")
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Encrypted DataFrames go through `@cache.secure`, which takes any serializer. A file backend keeps this example self-contained; production uses Redis or cachekit.io:
|
|
273
|
+
|
|
274
|
+
```python
|
|
275
|
+
import tempfile
|
|
276
|
+
from cachekit.backends.file import FileBackend, FileBackendConfig
|
|
277
|
+
from cachekit.serializers import ArrowSerializer
|
|
278
|
+
|
|
279
|
+
calls = 0
|
|
280
|
+
|
|
281
|
+
@cache.secure(
|
|
282
|
+
master_key=secret_key,
|
|
283
|
+
serializer=ArrowSerializer(),
|
|
284
|
+
backend=FileBackend(FileBackendConfig(cache_dir=tempfile.mkdtemp())),
|
|
285
|
+
)
|
|
286
|
+
def get_patient_data(hospital_id: int):
|
|
287
|
+
global calls
|
|
288
|
+
calls += 1
|
|
289
|
+
return pd.DataFrame({"hospital_id": [hospital_id], "patients": [42]})
|
|
290
|
+
|
|
291
|
+
get_patient_data(7)
|
|
292
|
+
get_patient_data(7) # second call is served from the encrypted cache
|
|
293
|
+
assert calls == 1
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
</details>
|
|
297
|
+
|
|
298
|
+
### Integrity Checking
|
|
299
|
+
|
|
300
|
+
> [!IMPORTANT]
|
|
301
|
+
> All serializers support **configurable checksums** for corruption detection using xxHash3-64 (8 bytes). Enabled by default in `@cache.production` and `@cache.secure`.
|
|
302
|
+
|
|
303
|
+
**Performance Impact** (benchmark-proven):
|
|
304
|
+
|
|
305
|
+
| Data Type | Latency Reduction (disabled) | Size Overhead |
|
|
306
|
+
|:----------|:----------------------------:|:-------------:|
|
|
307
|
+
| MessagePack (default) | 60-90% | 8 bytes |
|
|
308
|
+
| Arrow DataFrames | 35-49% | 8 bytes |
|
|
309
|
+
| JSON (orjson) | 37-68% | 8 bytes |
|
|
310
|
+
|
|
311
|
+
### Security
|
|
312
|
+
|
|
313
|
+
> [!CAUTION]
|
|
314
|
+
> When handling PII, medical, or financial data, always use `@cache.secure` to enforce encryption.
|
|
315
|
+
|
|
316
|
+
**Key rotation**: keep a retiring key readable with
|
|
317
|
+
`CACHEKIT_PREVIOUS_MASTER_KEYS` (comma-separated hex, max 3 decrypt-only keys)
|
|
318
|
+
while new writes use `CACHEKIT_MASTER_KEY`. A one-deploy key swap is not
|
|
319
|
+
zero-miss; follow the [key rotation
|
|
320
|
+
runbook](https://docs.cachekit.io/concepts/key-rotation/), including its Before
|
|
321
|
+
You Rotate checks. CK-framed entries are selected by exact key fingerprint —
|
|
322
|
+
never trial decryption; Interop-mode entries carry no CK frame and attempt
|
|
323
|
+
keyring keys sequentially instead.
|
|
324
|
+
|
|
325
|
+
cachekit employs comprehensive security tooling:
|
|
326
|
+
|
|
327
|
+
- **Dependency Security**: cargo-deny for license compliance + cargo-audit for RustSec scanning
|
|
328
|
+
- **Formal Verification**: Kani proves correctness of compression, checksums, encryption
|
|
329
|
+
- **Runtime Analysis**: Miri + sanitizers for memory safety
|
|
330
|
+
- **Fuzzing**: Coverage-guided testing with >80% code coverage
|
|
331
|
+
- **Zero CVEs**: Continuous vulnerability scanning
|
|
332
|
+
|
|
333
|
+
<details>
|
|
334
|
+
<summary><strong>Security Commands</strong></summary>
|
|
335
|
+
|
|
336
|
+
```bash
|
|
337
|
+
make security-install # Install security tools (one-time)
|
|
338
|
+
make security-fast # Run fast checks (< 3 min)
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
**Security Tiers:**
|
|
342
|
+
|
|
343
|
+
| Tier | Time | Coverage |
|
|
344
|
+
|:-----|:----:|:---------|
|
|
345
|
+
| Fast | < 3 min | Vulnerability scanning, license checks, linting |
|
|
346
|
+
| Medium | < 15 min | Unsafe code analysis, API stability, Miri subset |
|
|
347
|
+
| Deep | < 2 hours | Formal verification, extended fuzzing, full sanitizers |
|
|
348
|
+
|
|
349
|
+
See [SECURITY.md][security-url] for vulnerability reporting and detailed documentation.
|
|
350
|
+
|
|
351
|
+
</details>
|
|
352
|
+
|
|
353
|
+
### Monitoring & Observability
|
|
354
|
+
|
|
355
|
+
- **Per-function statistics** - `cache_info()` on every decorated function, modelled on `functools.lru_cache`
|
|
356
|
+
- **Prometheus metrics** - Recorded by default (your app owns exposition)
|
|
357
|
+
- **Structured logging** - Context-aware, per-operation fields
|
|
358
|
+
- **Health checks** - Comprehensive status endpoints
|
|
359
|
+
|
|
360
|
+
Every decorated function exposes `cache_info()`, returning a `CacheInfo` named tuple with
|
|
361
|
+
hit/miss counts, the L1/L2 split, and average backend latency:
|
|
362
|
+
|
|
363
|
+
```python
|
|
364
|
+
@cache()
|
|
365
|
+
def get_score(x):
|
|
366
|
+
return x ** 2
|
|
367
|
+
|
|
368
|
+
get_score(2)
|
|
369
|
+
get_score(2) # served from cache
|
|
370
|
+
|
|
371
|
+
info = get_score.cache_info()
|
|
372
|
+
# CacheInfo has 9 fields: hits, misses, l1_hits, l2_hits, maxsize,
|
|
373
|
+
# currsize, l2_avg_latency_ms, last_operation_at, session_id
|
|
374
|
+
assert info.l1_hits + info.l2_hits == info.hits # every hit is L1 or L2
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
`maxsize` and `currsize` are always `None` (the cache lives in an external store, not a
|
|
378
|
+
bounded in-process dict); they exist only for `lru_cache` API parity. See the
|
|
379
|
+
[API Reference](docs/api-reference.md#per-function-statistics-via-cache_info) for the full
|
|
380
|
+
field reference and a sample stats endpoint, and the
|
|
381
|
+
[Prometheus Metrics guide](docs/features/prometheus-metrics.md) for metric names and
|
|
382
|
+
exposition setup.
|
|
383
|
+
|
|
384
|
+
<details>
|
|
385
|
+
<summary><strong>Thread Safety Details</strong></summary>
|
|
386
|
+
|
|
387
|
+
**Free-threaded CPython (3.14t):** the core suites run green on
|
|
388
|
+
free-threaded 3.14 with the GIL verified disabled (CI job
|
|
389
|
+
`test-freethreaded`), and the Rust extension declares free-threaded safety
|
|
390
|
+
(`gil_used = false`). Free-threaded wheels are **not yet published** and
|
|
391
|
+
free-threaded builds are not officially supported — blocked upstream on
|
|
392
|
+
orjson (no free-threaded wheels) and hiredis (re-enables the GIL on import). See
|
|
393
|
+
[measured performance results](docs/free-threading.md#measured-performance) and the
|
|
394
|
+
full concurrency audit: [docs/free-threading.md](docs/free-threading.md).
|
|
395
|
+
|
|
396
|
+
**Per-Function Statistics:**
|
|
397
|
+
- Statistics tracked per function identity (`module.qualname`), shared across all calls and across re-decorations of the same function
|
|
398
|
+
- Thread-safe via RLock (all methods safe for concurrent access)
|
|
399
|
+
- Fork-safe: a forked child starts with zeroed counters and its own session ID
|
|
400
|
+
|
|
401
|
+
```python
|
|
402
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
403
|
+
|
|
404
|
+
@cache()
|
|
405
|
+
def expensive_func(x):
|
|
406
|
+
return x ** 2
|
|
407
|
+
|
|
408
|
+
# All threads share same stats
|
|
409
|
+
with ThreadPoolExecutor(max_workers=10) as executor:
|
|
410
|
+
results = list(executor.map(expensive_func, range(100)))
|
|
411
|
+
|
|
412
|
+
info = expensive_func.cache_info()
|
|
413
|
+
# CacheInfo(hits=..., misses=..., l1_hits=..., l2_hits=...,
|
|
414
|
+
# maxsize=None, currsize=None, l2_avg_latency_ms=...,
|
|
415
|
+
# last_operation_at=..., session_id=...)
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
</details>
|
|
419
|
+
|
|
420
|
+
---
|
|
421
|
+
|
|
422
|
+
## Documentation
|
|
423
|
+
|
|
424
|
+
### Start Here
|
|
425
|
+
|
|
426
|
+
| Guide | Description |
|
|
427
|
+
|:------|:------------|
|
|
428
|
+
| [Comparison Guide][comparison-url] | How cachekit compares to lru_cache, aiocache, cachetools |
|
|
429
|
+
| [Getting Started][getting-started-url] | Progressive tutorial from basics to advanced |
|
|
430
|
+
| [API Reference][api-reference-url] | Complete API documentation |
|
|
431
|
+
| [Skyline (live example)][skyline-url] | Canonical example project: this SDK ingests the Bluesky firehose and writes the analytics entries a TypeScript edge Worker serves live, on one shared interop namespace |
|
|
432
|
+
|
|
433
|
+
### Feature Deep Dives
|
|
434
|
+
|
|
435
|
+
| Feature | Description |
|
|
436
|
+
|:--------|:------------|
|
|
437
|
+
| [Serializer Guide][serializer-guide-url] | ArrowSerializer vs StandardSerializer benchmarks |
|
|
438
|
+
| [Circuit Breaker][circuit-breaker-url] | Prevent cascading failures |
|
|
439
|
+
| [Distributed Locking][distributed-locking-url] | Cache stampede prevention |
|
|
440
|
+
| [Prometheus Metrics][prometheus-url] | Built-in observability |
|
|
441
|
+
| [Zero-Knowledge Encryption][encryption-url] | Client-side security |
|
|
442
|
+
| [Interop Mode][interop-url] | Cross-SDK cache sharing with cachekit-ts/rs |
|
|
443
|
+
| [L1 Invalidation & SWR][l1-invalidation-url] | Invalidation scope (incl. cross-process whole-function on tenant-scoped Redis from the environment or `RedisBackendProvider`), stale-while-revalidate |
|
|
444
|
+
| [Reference Caching][reference-caching-url] | `@cache.local()` for non-serializable objects |
|
|
445
|
+
| [Rust Serialization][rust-serialization-url] | ByteStorage layer: LZ4, xxHash3, AES-256-GCM |
|
|
446
|
+
| [SSRF Protection][ssrf-url] | URL allowlisting for the CachekitIO backend |
|
|
447
|
+
|
|
448
|
+
---
|
|
449
|
+
|
|
450
|
+
## Configuration
|
|
451
|
+
|
|
452
|
+
### Environment Variables
|
|
453
|
+
|
|
454
|
+
```bash
|
|
455
|
+
# Redis Connection (priority: CACHEKIT_REDIS_URL > REDIS_URL)
|
|
456
|
+
CACHEKIT_REDIS_URL="redis://localhost:6379" # Primary (preferred)
|
|
457
|
+
REDIS_URL="redis://localhost:6379" # Fallback
|
|
458
|
+
|
|
459
|
+
# CachekitIO SaaS Backend (closed beta — request access at cachekit.io)
|
|
460
|
+
CACHEKIT_API_KEY="your-api-key" # For @cache.io() — or pass api_key= directly # pragma: allowlist secret
|
|
461
|
+
CACHEKIT_API_URL="https://api.cachekit.io" # Default SaaS endpoint
|
|
462
|
+
|
|
463
|
+
# Memcached Backend (optional: pip install cachekit[memcached])
|
|
464
|
+
CACHEKIT_MEMCACHED_SERVERS='["mc1:11211", "mc2:11211"]' # Default: 127.0.0.1:11211
|
|
465
|
+
CACHEKIT_MEMCACHED_CONNECT_TIMEOUT=2.0 # Default: 2.0 seconds
|
|
466
|
+
CACHEKIT_MEMCACHED_TIMEOUT=1.0 # Default: 1.0 seconds
|
|
467
|
+
CACHEKIT_MEMCACHED_KEY_PREFIX="myapp:" # Default: "" (none)
|
|
468
|
+
|
|
469
|
+
# Optional Configuration
|
|
470
|
+
CACHEKIT_MAX_VALUE_SIZE=104857600
|
|
471
|
+
CACHEKIT_ARROW_COMPRESSION=zstd
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
> [!NOTE]
|
|
475
|
+
> If both `CACHEKIT_REDIS_URL` and `REDIS_URL` are set, `CACHEKIT_REDIS_URL` takes precedence.
|
|
476
|
+
|
|
477
|
+
---
|
|
478
|
+
|
|
479
|
+
## Development
|
|
480
|
+
|
|
481
|
+
```bash
|
|
482
|
+
git clone https://github.com/cachekit-io/cachekit-py.git
|
|
483
|
+
cd cachekit-py
|
|
484
|
+
uv sync && make install
|
|
485
|
+
make quick-check # format + lint + critical tests
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
See [CONTRIBUTING.md][contributing-url] for full development guidelines.
|
|
489
|
+
|
|
490
|
+
---
|
|
491
|
+
|
|
492
|
+
## Requirements
|
|
493
|
+
|
|
494
|
+
| Component | Version |
|
|
495
|
+
|:----------|:--------|
|
|
496
|
+
| Python | 3.10+ |
|
|
497
|
+
|
|
498
|
+
---
|
|
499
|
+
|
|
500
|
+
## License
|
|
501
|
+
|
|
502
|
+
MIT License - see [LICENSE][license-file-url] for details.
|
|
503
|
+
|
|
504
|
+
---
|
|
505
|
+
|
|
506
|
+
<div align="center">
|
|
507
|
+
|
|
508
|
+
**[PyPI][pypi-url]** · **[GitHub][github-url]** · **[Issues][issues-url]**
|
|
509
|
+
|
|
510
|
+
</div>
|
|
511
|
+
|
|
512
|
+
<!-- Reference Links -->
|
|
513
|
+
[pypi-badge]: https://img.shields.io/pypi/v/cachekit.svg
|
|
514
|
+
[python-badge]: https://img.shields.io/pypi/pyversions/cachekit.svg
|
|
515
|
+
[license-badge]: https://img.shields.io/badge/License-MIT-yellow.svg
|
|
516
|
+
[pypi-url]: https://pypi.org/project/cachekit/
|
|
517
|
+
[license-url]: https://opensource.org/licenses/MIT
|
|
518
|
+
[uv-url]: https://github.com/astral-sh/uv
|
|
519
|
+
[security-url]: SECURITY.md
|
|
520
|
+
[comparison-url]: docs/comparison.md
|
|
521
|
+
[skyline-url]: https://github.com/cachekit-io/bluesky-thinking
|
|
522
|
+
[getting-started-url]: docs/getting-started.md
|
|
523
|
+
[api-reference-url]: docs/api-reference.md
|
|
524
|
+
[serializer-guide-url]: docs/serializers/index.md
|
|
525
|
+
[circuit-breaker-url]: docs/features/circuit-breaker.md
|
|
526
|
+
[distributed-locking-url]: docs/features/distributed-locking.md
|
|
527
|
+
[prometheus-url]: docs/features/prometheus-metrics.md
|
|
528
|
+
[encryption-url]: docs/features/zero-knowledge-encryption.md
|
|
529
|
+
[interop-url]: docs/features/interop-mode.md
|
|
530
|
+
[l1-invalidation-url]: docs/features/l1-invalidation.md
|
|
531
|
+
[reference-caching-url]: docs/features/reference-caching.md
|
|
532
|
+
[rust-serialization-url]: docs/features/rust-serialization.md
|
|
533
|
+
[ssrf-url]: docs/features/ssrf-protection.md
|
|
534
|
+
[contributing-url]: CONTRIBUTING.md
|
|
535
|
+
[license-file-url]: LICENSE
|
|
536
|
+
[github-url]: https://github.com/cachekit-io/cachekit-py
|
|
537
|
+
[issues-url]: https://github.com/cachekit-io/cachekit-py/issues
|
|
538
|
+
[codecov-badge]: https://codecov.io/github/cachekit-io/cachekit-py/graph/badge.svg
|
|
539
|
+
[codecov-url]: https://codecov.io/github/cachekit-io/cachekit-py
|
cachekit/__init__.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
r"""cachekit - Caching decorator for Python applications.
|
|
2
|
+
|
|
3
|
+
Backend-agnostic caching with intent-based decorators — circuit breaker,
|
|
4
|
+
distributed locking, Prometheus metrics, and optional zero-knowledge
|
|
5
|
+
AES-256-GCM encryption, on a Rust-powered core.
|
|
6
|
+
|
|
7
|
+
Zero-config L1 in-memory by default; pluggable backends: Redis, Memcached,
|
|
8
|
+
File, CachekitIO SaaS, and custom via the BaseBackend protocol.
|
|
9
|
+
|
|
10
|
+
Key Features:
|
|
11
|
+
- **Intelligent @cache decorator** with auto-detection and intent-based optimization
|
|
12
|
+
- **Circuit breaker protection** against cascading failures
|
|
13
|
+
- **Backpressure control** to prevent backend overload
|
|
14
|
+
- **Connection pooling** for optimized performance
|
|
15
|
+
- **Health check methods** for comprehensive monitoring
|
|
16
|
+
- **Structured logging** with per-operation fields
|
|
17
|
+
- **Statistics collection** for Prometheus metrics integration
|
|
18
|
+
|
|
19
|
+
Architecture Overview:
|
|
20
|
+
cachekit provides a modular decorator architecture with intelligent
|
|
21
|
+
auto-detection and intent-based optimization:
|
|
22
|
+
|
|
23
|
+
- FeatureOrchestrator: Manages enterprise-grade reliability and monitoring features
|
|
24
|
+
- Flexible configuration interface with intelligent auto-detection
|
|
25
|
+
- Enhanced error handling with comprehensive safety checks
|
|
26
|
+
- Pluggable backend abstraction (Redis, Memcached, File, CachekitIO, custom)
|
|
27
|
+
|
|
28
|
+
Born from production debugging of caching failures:
|
|
29
|
+
- UTF-8 corruption prevention with intelligent binary data handling
|
|
30
|
+
- Binary data magic byte detection and validation
|
|
31
|
+
- Chunked storage for large objects with atomic operations
|
|
32
|
+
- Comprehensive checksum validation and recovery
|
|
33
|
+
|
|
34
|
+
Example Usage:
|
|
35
|
+
```python
|
|
36
|
+
from cachekit import cache
|
|
37
|
+
from cachekit.config.nested import CircuitBreakerConfig
|
|
38
|
+
|
|
39
|
+
# Intelligent cache with zero configuration (90% of use cases)
|
|
40
|
+
@cache
|
|
41
|
+
def expensive_function():
|
|
42
|
+
return compute_result()
|
|
43
|
+
|
|
44
|
+
# Intent-based optimization (9% of use cases)
|
|
45
|
+
@cache.minimal # Speed-critical functions
|
|
46
|
+
def get_price(symbol: str):
|
|
47
|
+
return fetch_price(symbol)
|
|
48
|
+
|
|
49
|
+
@cache.production # Reliability-critical functions
|
|
50
|
+
def process_payment(amount: Decimal):
|
|
51
|
+
return payment_gateway.charge(amount)
|
|
52
|
+
|
|
53
|
+
@cache.secure # Security-critical functions
|
|
54
|
+
def get_user_data(user_id: int) -> UserProfile:
|
|
55
|
+
return db.fetch_user(user_id)
|
|
56
|
+
|
|
57
|
+
@cache.io() # Managed SaaS backend (cachekit.io, closed beta)
|
|
58
|
+
def get_remote_data(key: str):
|
|
59
|
+
return fetch_remote(key)
|
|
60
|
+
|
|
61
|
+
# Manual configuration when needed (1% of use cases)
|
|
62
|
+
@cache(ttl=3600, namespace="custom", circuit_breaker=CircuitBreakerConfig(failure_threshold=10))
|
|
63
|
+
def custom_function():
|
|
64
|
+
return special_computation()
|
|
65
|
+
|
|
66
|
+
# Health monitoring
|
|
67
|
+
health = custom_function.get_health_status()
|
|
68
|
+
full_health = custom_function.check_health()
|
|
69
|
+
```
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
__version__ = "0.20.0"
|
|
73
|
+
|
|
74
|
+
from collections.abc import Callable
|
|
75
|
+
from typing import Any, TypeVar
|
|
76
|
+
|
|
77
|
+
# Configure hiredis compatibility BEFORE any Redis imports
|
|
78
|
+
# This prevents GIL warnings in Python 3.13+ free-threading mode
|
|
79
|
+
try:
|
|
80
|
+
from . import hiredis_compat
|
|
81
|
+
except ImportError:
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
# Import the configuration classes
|
|
85
|
+
# Redis client is automatically fast - no special import needed
|
|
86
|
+
from .config import DecoratorConfig
|
|
87
|
+
|
|
88
|
+
# Import the intelligent cache decorator and CacheInfo
|
|
89
|
+
from .decorators import cache
|
|
90
|
+
from .decorators.wrapper import CacheInfo
|
|
91
|
+
|
|
92
|
+
# Import health check functionality
|
|
93
|
+
from .health import (
|
|
94
|
+
HealthCheckResult,
|
|
95
|
+
HealthLevel,
|
|
96
|
+
HealthStatus,
|
|
97
|
+
async_health_check_handler,
|
|
98
|
+
get_health_checker,
|
|
99
|
+
health_check_handler,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# Interop mode (interop/v1): cross-SDK key generation and value codec.
|
|
103
|
+
# The decorator API is @cache(interop="op", namespace="users"); these helpers are
|
|
104
|
+
# for manual key/value handling (debugging, out-of-band writers, migrations).
|
|
105
|
+
from .interop import (
|
|
106
|
+
InteropError,
|
|
107
|
+
decode_interop_value,
|
|
108
|
+
encode_interop_value,
|
|
109
|
+
generate_interop_key,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# L1/L2 architecture integrated into standard cache interface
|
|
113
|
+
# No separate imports needed - cache.minimal/.production/.secure handle L1+L2 transparently
|
|
114
|
+
# Import reliability configuration
|
|
115
|
+
from .reliability import CircuitBreakerConfig
|
|
116
|
+
|
|
117
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
__all__ = [
|
|
121
|
+
"__version__",
|
|
122
|
+
"async_health_check_handler",
|
|
123
|
+
"cache",
|
|
124
|
+
"CacheInfo",
|
|
125
|
+
"CircuitBreakerConfig",
|
|
126
|
+
"DecoratorConfig",
|
|
127
|
+
"decode_interop_value",
|
|
128
|
+
"encode_interop_value",
|
|
129
|
+
"generate_interop_key",
|
|
130
|
+
"get_health_checker",
|
|
131
|
+
"health_check_handler",
|
|
132
|
+
"HealthCheckResult",
|
|
133
|
+
"HealthLevel",
|
|
134
|
+
"HealthStatus",
|
|
135
|
+
"InteropError",
|
|
136
|
+
]
|
|
Binary file
|