cachekit 0.6.1__tar.gz → 0.8.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.
- {cachekit-0.6.1 → cachekit-0.8.0}/Cargo.lock +127 -9
- {cachekit-0.6.1 → cachekit-0.8.0}/PKG-INFO +1 -1
- {cachekit-0.6.1 → cachekit-0.8.0}/pyproject.toml +1 -1
- {cachekit-0.6.1 → cachekit-0.8.0}/rust/Cargo.toml +2 -2
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/__init__.py +1 -1
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/backends/base.py +19 -3
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/backends/cachekitio/backend.py +97 -22
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/backends/provider.py +79 -12
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/backends/redis/provider.py +9 -2
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/cache_handler.py +23 -4
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/config/decorator.py +7 -2
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/config/nested.py +7 -2
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/decorators/wrapper.py +74 -77
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/key_generator.py +27 -2
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/reliability/adaptive_timeout.py +10 -8
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/serializers/__init__.py +2 -1
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/serializers/auto_serializer.py +32 -1
- {cachekit-0.6.1 → cachekit-0.8.0}/Cargo.toml +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/LICENSE +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/README.md +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/rust/Makefile +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/rust/README.md +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/rust/TEST_EXPANSION_SUMMARY.md +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/rust/src/lib.rs +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/rust/src/python_bindings.rs +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/rust/supply-chain/audits.toml +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/rust/supply-chain/config.toml +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/rust/supply-chain/imports.lock +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/rust/tsan_suppressions.txt +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/backends/__init__.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/backends/base_config.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/backends/cachekitio/__init__.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/backends/cachekitio/client.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/backends/cachekitio/config.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/backends/cachekitio/error_handler.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/backends/cachekitio/session.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/backends/errors.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/backends/file/__init__.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/backends/file/backend.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/backends/file/config.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/backends/memcached/__init__.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/backends/memcached/backend.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/backends/memcached/config.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/backends/memcached/error_handler.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/backends/redis/__init__.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/backends/redis/backend.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/backends/redis/client.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/backends/redis/config.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/backends/redis/error_handler.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/config/__init__.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/config/settings.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/config/singleton.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/config/validation.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/decorators/__init__.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/decorators/intent.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/decorators/local_wrapper.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/decorators/main.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/decorators/orchestrator.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/decorators/session.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/decorators/stats_context.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/decorators/tenant_context.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/decorators/utils/__init__.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/di.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/hash_utils.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/health.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/hiredis_compat.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/imports.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/invalidation/__init__.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/invalidation/channel.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/invalidation/event.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/invalidation/redis_channel.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/l1_cache.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/logging.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/monitoring/__init__.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/monitoring/correlation_tracking.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/monitoring/pool_monitor.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/monitoring/protocols.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/object_cache.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/py.typed +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/reliability/__init__.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/reliability/async_metrics.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/reliability/circuit_breaker.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/reliability/error_classification.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/reliability/load_control.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/reliability/metrics_collection.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/reliability/profiles.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/serializers/arrow_serializer.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/serializers/base.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/serializers/encryption_wrapper.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/serializers/orjson_serializer.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/serializers/standard_serializer.py +0 -0
- {cachekit-0.6.1 → cachekit-0.8.0}/src/cachekit/serializers/wrapper.py +0 -0
|
@@ -17,6 +17,43 @@ version = "2.0.1"
|
|
|
17
17
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
18
18
|
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
|
19
19
|
|
|
20
|
+
[[package]]
|
|
21
|
+
name = "aead"
|
|
22
|
+
version = "0.5.2"
|
|
23
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
24
|
+
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
|
|
25
|
+
dependencies = [
|
|
26
|
+
"crypto-common",
|
|
27
|
+
"generic-array",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[[package]]
|
|
31
|
+
name = "aes"
|
|
32
|
+
version = "0.8.4"
|
|
33
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
34
|
+
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
|
|
35
|
+
dependencies = [
|
|
36
|
+
"cfg-if",
|
|
37
|
+
"cipher",
|
|
38
|
+
"cpufeatures",
|
|
39
|
+
"zeroize",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[[package]]
|
|
43
|
+
name = "aes-gcm"
|
|
44
|
+
version = "0.10.3"
|
|
45
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
46
|
+
checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
|
|
47
|
+
dependencies = [
|
|
48
|
+
"aead",
|
|
49
|
+
"aes",
|
|
50
|
+
"cipher",
|
|
51
|
+
"ctr",
|
|
52
|
+
"ghash",
|
|
53
|
+
"subtle",
|
|
54
|
+
"zeroize",
|
|
55
|
+
]
|
|
56
|
+
|
|
20
57
|
[[package]]
|
|
21
58
|
name = "ahash"
|
|
22
59
|
version = "0.8.12"
|
|
@@ -208,14 +245,17 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
|
|
|
208
245
|
|
|
209
246
|
[[package]]
|
|
210
247
|
name = "cachekit-core"
|
|
211
|
-
version = "0.
|
|
248
|
+
version = "0.2.0"
|
|
212
249
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
213
|
-
checksum = "
|
|
250
|
+
checksum = "9622b3c3e47fcf7abfde287c557e6171674e37842a2dd26fda03f63720542f17"
|
|
214
251
|
dependencies = [
|
|
252
|
+
"aes",
|
|
253
|
+
"aes-gcm",
|
|
215
254
|
"byteorder",
|
|
216
255
|
"bytes",
|
|
217
256
|
"cbindgen",
|
|
218
257
|
"generic-array",
|
|
258
|
+
"getrandom 0.2.17",
|
|
219
259
|
"hkdf",
|
|
220
260
|
"hmac",
|
|
221
261
|
"lz4_flex",
|
|
@@ -224,14 +264,14 @@ dependencies = [
|
|
|
224
264
|
"serde",
|
|
225
265
|
"serde_bytes",
|
|
226
266
|
"sha2",
|
|
227
|
-
"thiserror
|
|
267
|
+
"thiserror 2.0.18",
|
|
228
268
|
"xxhash-rust",
|
|
229
269
|
"zeroize",
|
|
230
270
|
]
|
|
231
271
|
|
|
232
272
|
[[package]]
|
|
233
273
|
name = "cachekit-rs"
|
|
234
|
-
version = "0.
|
|
274
|
+
version = "0.8.0"
|
|
235
275
|
dependencies = [
|
|
236
276
|
"cachekit-core",
|
|
237
277
|
"criterion",
|
|
@@ -312,6 +352,16 @@ dependencies = [
|
|
|
312
352
|
"half",
|
|
313
353
|
]
|
|
314
354
|
|
|
355
|
+
[[package]]
|
|
356
|
+
name = "cipher"
|
|
357
|
+
version = "0.4.4"
|
|
358
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
359
|
+
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
|
360
|
+
dependencies = [
|
|
361
|
+
"crypto-common",
|
|
362
|
+
"inout",
|
|
363
|
+
]
|
|
364
|
+
|
|
315
365
|
[[package]]
|
|
316
366
|
name = "clap"
|
|
317
367
|
version = "4.6.0"
|
|
@@ -444,6 +494,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
444
494
|
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
|
|
445
495
|
dependencies = [
|
|
446
496
|
"generic-array",
|
|
497
|
+
"rand_core 0.6.4",
|
|
447
498
|
"typenum",
|
|
448
499
|
]
|
|
449
500
|
|
|
@@ -463,6 +514,15 @@ version = "0.0.6"
|
|
|
463
514
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
464
515
|
checksum = "e2931af7e13dc045d8e9d26afccc6fa115d64e115c9c84b1166288b46f6782c2"
|
|
465
516
|
|
|
517
|
+
[[package]]
|
|
518
|
+
name = "ctr"
|
|
519
|
+
version = "0.9.2"
|
|
520
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
521
|
+
checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
|
|
522
|
+
dependencies = [
|
|
523
|
+
"cipher",
|
|
524
|
+
]
|
|
525
|
+
|
|
466
526
|
[[package]]
|
|
467
527
|
name = "debugid"
|
|
468
528
|
version = "0.8.0"
|
|
@@ -639,8 +699,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
639
699
|
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
|
640
700
|
dependencies = [
|
|
641
701
|
"cfg-if",
|
|
702
|
+
"js-sys",
|
|
642
703
|
"libc",
|
|
643
704
|
"wasi",
|
|
705
|
+
"wasm-bindgen",
|
|
644
706
|
]
|
|
645
707
|
|
|
646
708
|
[[package]]
|
|
@@ -668,6 +730,16 @@ dependencies = [
|
|
|
668
730
|
"wasip3",
|
|
669
731
|
]
|
|
670
732
|
|
|
733
|
+
[[package]]
|
|
734
|
+
name = "ghash"
|
|
735
|
+
version = "0.5.1"
|
|
736
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
737
|
+
checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1"
|
|
738
|
+
dependencies = [
|
|
739
|
+
"opaque-debug",
|
|
740
|
+
"polyval",
|
|
741
|
+
]
|
|
742
|
+
|
|
671
743
|
[[package]]
|
|
672
744
|
name = "gimli"
|
|
673
745
|
version = "0.32.3"
|
|
@@ -820,6 +892,15 @@ dependencies = [
|
|
|
820
892
|
"str_stack",
|
|
821
893
|
]
|
|
822
894
|
|
|
895
|
+
[[package]]
|
|
896
|
+
name = "inout"
|
|
897
|
+
version = "0.1.4"
|
|
898
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
899
|
+
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
|
|
900
|
+
dependencies = [
|
|
901
|
+
"generic-array",
|
|
902
|
+
]
|
|
903
|
+
|
|
823
904
|
[[package]]
|
|
824
905
|
name = "is-terminal"
|
|
825
906
|
version = "0.4.17"
|
|
@@ -909,9 +990,9 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
|
|
909
990
|
|
|
910
991
|
[[package]]
|
|
911
992
|
name = "lz4_flex"
|
|
912
|
-
version = "0.
|
|
993
|
+
version = "0.12.2"
|
|
913
994
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
914
|
-
checksum = "
|
|
995
|
+
checksum = "90071f8077f8e40adfc4b7fe9cd495ce316263f19e75c2211eeff3fdf475a3d9"
|
|
915
996
|
dependencies = [
|
|
916
997
|
"twox-hash",
|
|
917
998
|
]
|
|
@@ -1006,6 +1087,12 @@ version = "11.1.5"
|
|
|
1006
1087
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1007
1088
|
checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
|
|
1008
1089
|
|
|
1090
|
+
[[package]]
|
|
1091
|
+
name = "opaque-debug"
|
|
1092
|
+
version = "0.3.1"
|
|
1093
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1094
|
+
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
|
1095
|
+
|
|
1009
1096
|
[[package]]
|
|
1010
1097
|
name = "plotters"
|
|
1011
1098
|
version = "0.3.7"
|
|
@@ -1034,6 +1121,18 @@ dependencies = [
|
|
|
1034
1121
|
"plotters-backend",
|
|
1035
1122
|
]
|
|
1036
1123
|
|
|
1124
|
+
[[package]]
|
|
1125
|
+
name = "polyval"
|
|
1126
|
+
version = "0.6.2"
|
|
1127
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1128
|
+
checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
|
|
1129
|
+
dependencies = [
|
|
1130
|
+
"cfg-if",
|
|
1131
|
+
"cpufeatures",
|
|
1132
|
+
"opaque-debug",
|
|
1133
|
+
"universal-hash",
|
|
1134
|
+
]
|
|
1135
|
+
|
|
1037
1136
|
[[package]]
|
|
1038
1137
|
name = "portable-atomic"
|
|
1039
1138
|
version = "1.13.1"
|
|
@@ -1290,7 +1389,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
1290
1389
|
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
|
1291
1390
|
dependencies = [
|
|
1292
1391
|
"rand_chacha",
|
|
1293
|
-
"rand_core",
|
|
1392
|
+
"rand_core 0.9.5",
|
|
1294
1393
|
]
|
|
1295
1394
|
|
|
1296
1395
|
[[package]]
|
|
@@ -1300,7 +1399,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
1300
1399
|
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
|
1301
1400
|
dependencies = [
|
|
1302
1401
|
"ppv-lite86",
|
|
1303
|
-
"rand_core",
|
|
1402
|
+
"rand_core 0.9.5",
|
|
1403
|
+
]
|
|
1404
|
+
|
|
1405
|
+
[[package]]
|
|
1406
|
+
name = "rand_core"
|
|
1407
|
+
version = "0.6.4"
|
|
1408
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1409
|
+
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
|
1410
|
+
dependencies = [
|
|
1411
|
+
"getrandom 0.2.17",
|
|
1304
1412
|
]
|
|
1305
1413
|
|
|
1306
1414
|
[[package]]
|
|
@@ -1318,7 +1426,7 @@ version = "0.4.0"
|
|
|
1318
1426
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1319
1427
|
checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a"
|
|
1320
1428
|
dependencies = [
|
|
1321
|
-
"rand_core",
|
|
1429
|
+
"rand_core 0.9.5",
|
|
1322
1430
|
]
|
|
1323
1431
|
|
|
1324
1432
|
[[package]]
|
|
@@ -1804,6 +1912,16 @@ version = "0.2.4"
|
|
|
1804
1912
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1805
1913
|
checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
|
|
1806
1914
|
|
|
1915
|
+
[[package]]
|
|
1916
|
+
name = "universal-hash"
|
|
1917
|
+
version = "0.5.1"
|
|
1918
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1919
|
+
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
|
|
1920
|
+
dependencies = [
|
|
1921
|
+
"crypto-common",
|
|
1922
|
+
"subtle",
|
|
1923
|
+
]
|
|
1924
|
+
|
|
1807
1925
|
[[package]]
|
|
1808
1926
|
name = "untrusted"
|
|
1809
1927
|
version = "0.9.0"
|
|
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "cachekit"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.8.0"
|
|
8
8
|
description = "Production-ready Redis caching for Python with intelligent reliability features and Rust-powered performance"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "cachekit-rs"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.8.0"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
authors = ["cachekit Contributors"]
|
|
6
6
|
description = "High-performance storage engine for caching with compression and encryption"
|
|
@@ -20,7 +20,7 @@ crate-type = ["cdylib", "rlib"]
|
|
|
20
20
|
|
|
21
21
|
[dependencies]
|
|
22
22
|
# Compression, checksums, encryption (https://crates.io/crates/cachekit-core)
|
|
23
|
-
cachekit-core = { version = "
|
|
23
|
+
cachekit-core = { version = "0.2.0", features = ["compression", "checksum", "messagepack", "encryption"] }
|
|
24
24
|
|
|
25
25
|
# Python integration - optional for Rust-only builds
|
|
26
26
|
pyo3 = { workspace = true, optional = true }
|
|
@@ -188,10 +188,23 @@ class LockableBackend(Protocol):
|
|
|
188
188
|
- Local-only: SQLite, FileSystem (single-process locking)
|
|
189
189
|
- Not supported: HTTP (stateless), Memcached, S3
|
|
190
190
|
|
|
191
|
+
Contract — bare cache key:
|
|
192
|
+
``acquire_lock`` receives the **bare cache key**, identical to what
|
|
193
|
+
``get``/``set``/``delete`` receive. Callers MUST NOT append suffixes
|
|
194
|
+
like ``:lock`` before passing the key. Each backend owns its own
|
|
195
|
+
lock-namespace derivation:
|
|
196
|
+
|
|
197
|
+
- Redis: derives ``<key>:lock`` internally for the on-wire lock name.
|
|
198
|
+
- CachekitIO (SaaS): the lock endpoint is ``POST /v1/cache/{key}/lock`` —
|
|
199
|
+
no key derivation needed.
|
|
200
|
+
|
|
201
|
+
This convergence keeps the Python SDK byte-for-byte compatible with
|
|
202
|
+
the cachekit-rs and cachekit-ts SDKs at the protocol boundary.
|
|
203
|
+
|
|
191
204
|
Example:
|
|
192
205
|
>>> # Distributed locking pattern (async context):
|
|
193
206
|
>>> # if hasattr(backend, 'acquire_lock'):
|
|
194
|
-
>>> # async with backend.acquire_lock("
|
|
207
|
+
>>> # async with backend.acquire_lock("user:123:profile", timeout=30) as acquired:
|
|
195
208
|
>>> # if acquired:
|
|
196
209
|
>>> # result = expensive_computation()
|
|
197
210
|
"""
|
|
@@ -205,7 +218,10 @@ class LockableBackend(Protocol):
|
|
|
205
218
|
"""Acquire a distributed lock on key.
|
|
206
219
|
|
|
207
220
|
Args:
|
|
208
|
-
key:
|
|
221
|
+
key: Bare cache key (e.g., ``"user:123"``) — the SAME shape passed
|
|
222
|
+
to ``get``/``set``/``delete``. Implementations are responsible
|
|
223
|
+
for any internal lock-namespace derivation; callers MUST NOT
|
|
224
|
+
pre-suffix the key.
|
|
209
225
|
timeout: How long to hold the lock (seconds) before auto-release
|
|
210
226
|
blocking_timeout: Max time to wait for lock acquisition (None = non-blocking)
|
|
211
227
|
|
|
@@ -217,7 +233,7 @@ class LockableBackend(Protocol):
|
|
|
217
233
|
BackendError: If backend operation fails
|
|
218
234
|
|
|
219
235
|
Example:
|
|
220
|
-
>>> async with backend.acquire_lock("
|
|
236
|
+
>>> async with backend.acquire_lock("user:123:profile", timeout=30, blocking_timeout=5) as acquired: # doctest: +SKIP
|
|
221
237
|
... if acquired: # doctest: +SKIP
|
|
222
238
|
... # Lock held, safe to proceed
|
|
223
239
|
... pass # doctest: +SKIP
|
|
@@ -2,16 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import asyncio
|
|
5
6
|
import json
|
|
7
|
+
import math
|
|
8
|
+
import random
|
|
6
9
|
import time
|
|
7
|
-
from
|
|
10
|
+
from collections.abc import AsyncIterator
|
|
11
|
+
from contextlib import asynccontextmanager
|
|
12
|
+
from typing import TYPE_CHECKING, Any, Optional
|
|
13
|
+
from urllib.parse import quote
|
|
8
14
|
|
|
9
15
|
import httpx
|
|
10
16
|
|
|
11
17
|
from cachekit.backends.cachekitio.client import get_cached_async_http_client, get_sync_http_client
|
|
12
18
|
from cachekit.backends.cachekitio.config import CachekitIOBackendConfig
|
|
13
19
|
from cachekit.backends.cachekitio.error_handler import classify_http_error
|
|
14
|
-
from cachekit.backends.errors import BackendError
|
|
20
|
+
from cachekit.backends.errors import BackendError, BackendErrorType
|
|
15
21
|
from cachekit.decorators.stats_context import get_current_function_stats
|
|
16
22
|
from cachekit.logging import get_structured_logger
|
|
17
23
|
|
|
@@ -544,42 +550,111 @@ class CachekitIOBackend:
|
|
|
544
550
|
|
|
545
551
|
# ==================== LockableBackend Protocol ====================
|
|
546
552
|
|
|
547
|
-
async def
|
|
548
|
-
"""
|
|
553
|
+
async def _try_acquire_lock(self, lock_key: str, timeout: float) -> str | None:
|
|
554
|
+
"""Single attempt at the SaaS lock endpoint. Returns lock_id, or None if held.
|
|
549
555
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
556
|
+
``lock_key`` is the bare cache key (LockableBackend contract). The SaaS lock
|
|
557
|
+
endpoint is ``POST /v1/cache/{key}/lock`` — no internal derivation is needed
|
|
558
|
+
because lock semantics live in the URL path, not the key namespace. Appending
|
|
559
|
+
``:lock`` here would push the SaaS validator past its 7-segment canonical key
|
|
560
|
+
budget and trigger a 400 at the edge.
|
|
553
561
|
|
|
554
|
-
|
|
555
|
-
|
|
562
|
+
Non-finite (NaN / ±inf) or non-positive ``timeout`` is clamped to 1ms — without
|
|
563
|
+
the guard, ``int(NaN)`` / ``int(inf)`` would raise outside ``BackendError`` and
|
|
564
|
+
escape the wrapper's degrade-to-no-lock branch.
|
|
565
|
+
|
|
566
|
+
Raises:
|
|
567
|
+
BackendError: For AUTHENTICATION and PERMANENT failures (bad key, bad key
|
|
568
|
+
format rejected by SaaS validator) — polling won't recover and the wrapper
|
|
569
|
+
degrades to no-lock execution. TRANSIENT/TIMEOUT/UNKNOWN are swallowed as
|
|
570
|
+
None so the polling loop can retry.
|
|
556
571
|
"""
|
|
572
|
+
# Clamp non-positive / non-finite timeouts before the int conversion.
|
|
573
|
+
# int(NaN) / int(inf) raise ValueError/OverflowError that aren't BackendError, so
|
|
574
|
+
# they'd escape the wrapper's degrade-to-no-lock branch and crash the @cache.io call.
|
|
575
|
+
timeout_ms = max(1, int(timeout * 1000)) if math.isfinite(timeout) else 1
|
|
576
|
+
encoded_key = quote(lock_key, safe="")
|
|
557
577
|
try:
|
|
558
|
-
payload = json.dumps({"timeout_ms": timeout * 1000})
|
|
559
578
|
response = await self._request_async(
|
|
560
579
|
"POST",
|
|
561
|
-
f"{
|
|
562
|
-
content=
|
|
580
|
+
f"{encoded_key}/lock",
|
|
581
|
+
content=json.dumps({"timeout_ms": timeout_ms}).encode(),
|
|
563
582
|
headers={"Content-Type": "application/json"},
|
|
564
583
|
)
|
|
584
|
+
except BackendError as exc:
|
|
585
|
+
# Re-raise unrecoverable failures so the wrapper can log + degrade once,
|
|
586
|
+
# instead of burning the full blocking_timeout on billable retries.
|
|
587
|
+
if exc.error_type in (BackendErrorType.AUTHENTICATION, BackendErrorType.PERMANENT):
|
|
588
|
+
raise
|
|
589
|
+
return None
|
|
590
|
+
|
|
591
|
+
try:
|
|
565
592
|
data = response.json()
|
|
566
|
-
|
|
567
|
-
except
|
|
593
|
+
lock_id = data.get("lock_id")
|
|
594
|
+
except (ValueError, AttributeError):
|
|
595
|
+
# Malformed body from the SaaS — treat as held (retry) rather than crashing
|
|
596
|
+
# the wrapper. Same failure class as the original issue #129 if we re-raised.
|
|
568
597
|
return None
|
|
569
598
|
|
|
570
|
-
|
|
571
|
-
|
|
599
|
+
return lock_id if isinstance(lock_id, str) else None
|
|
600
|
+
|
|
601
|
+
@asynccontextmanager
|
|
602
|
+
async def acquire_lock(
|
|
603
|
+
self,
|
|
604
|
+
key: str,
|
|
605
|
+
timeout: float,
|
|
606
|
+
blocking_timeout: Optional[float] = None,
|
|
607
|
+
) -> AsyncIterator[bool]:
|
|
608
|
+
"""Acquire distributed lock (LockableBackend protocol).
|
|
609
|
+
|
|
610
|
+
The SaaS endpoint returns immediately; client-side polling implements
|
|
611
|
+
``blocking_timeout`` with proportional jitter (0.5×–1× the capped delay) to
|
|
612
|
+
avoid lockstep retries on concurrent waiters. Each retry is a billable SaaS
|
|
613
|
+
request — keep the cap tight.
|
|
572
614
|
|
|
573
615
|
Args:
|
|
574
|
-
|
|
575
|
-
|
|
616
|
+
key: Lock key
|
|
617
|
+
timeout: Server-side hold duration before auto-release (seconds)
|
|
618
|
+
blocking_timeout: Max client-side wait to acquire (None = single attempt)
|
|
576
619
|
|
|
577
|
-
|
|
578
|
-
True if
|
|
620
|
+
Yields:
|
|
621
|
+
True if acquired, False if ``blocking_timeout`` elapsed without acquisition
|
|
622
|
+
"""
|
|
623
|
+
lock_id: str | None = None
|
|
624
|
+
try:
|
|
625
|
+
lock_id = await self._try_acquire_lock(key, timeout)
|
|
626
|
+
|
|
627
|
+
if lock_id is None and blocking_timeout is not None:
|
|
628
|
+
deadline = time.monotonic() + blocking_timeout
|
|
629
|
+
delay = 0.05
|
|
630
|
+
while lock_id is None:
|
|
631
|
+
remaining = deadline - time.monotonic()
|
|
632
|
+
if remaining <= 0:
|
|
633
|
+
break
|
|
634
|
+
# Proportional jitter (not crypto): spread concurrent waiters, 0.5×–1× the capped delay.
|
|
635
|
+
jitter = 0.5 + random.random() * 0.5 # noqa: S311 — backoff jitter, not security
|
|
636
|
+
await asyncio.sleep(min(delay, remaining) * jitter)
|
|
637
|
+
lock_id = await self._try_acquire_lock(key, timeout)
|
|
638
|
+
delay = min(delay * 2, 0.5)
|
|
639
|
+
|
|
640
|
+
yield lock_id is not None
|
|
641
|
+
finally:
|
|
642
|
+
if lock_id is not None:
|
|
643
|
+
await self._release_lock(key, lock_id)
|
|
644
|
+
|
|
645
|
+
async def _release_lock(self, lock_key: str, lock_id: str) -> bool:
|
|
646
|
+
"""Release distributed lock. Internal helper for ``acquire_lock``'s cleanup.
|
|
647
|
+
|
|
648
|
+
Best-effort: swallows ``BackendError`` and returns False so a release failure
|
|
649
|
+
inside ``__aexit__`` cannot mask the user's exception. The server-side ``timeout``
|
|
650
|
+
on the lock is the safety net if the DELETE never lands.
|
|
579
651
|
"""
|
|
652
|
+
# URL-encode both segments: lock_key is caller-controlled, lock_id is server-issued
|
|
653
|
+
# but the SaaS contract doesn't pin a charset.
|
|
654
|
+
encoded_key = quote(lock_key, safe="")
|
|
655
|
+
encoded_id = quote(lock_id, safe="")
|
|
580
656
|
try:
|
|
581
|
-
|
|
582
|
-
await self._request_async("DELETE", f"{lock_key}/lock?lock_id={lock_id}")
|
|
657
|
+
await self._request_async("DELETE", f"{encoded_key}/lock?lock_id={encoded_id}")
|
|
583
658
|
return True
|
|
584
659
|
except BackendError:
|
|
585
660
|
return False
|
|
@@ -102,32 +102,99 @@ class DefaultCacheClientProvider(CacheClientProvider):
|
|
|
102
102
|
|
|
103
103
|
|
|
104
104
|
class DefaultBackendProvider(BackendProviderInterface):
|
|
105
|
-
"""Default backend provider
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
105
|
+
"""Default backend provider with env-based auto-detection.
|
|
106
|
+
|
|
107
|
+
Selection is by a single, unambiguous environment signal. Priority order:
|
|
108
|
+
1. CACHEKIT_API_KEY → CachekitIOBackend (SaaS)
|
|
109
|
+
2. CACHEKIT_REDIS_URL → RedisBackend
|
|
110
|
+
3. CACHEKIT_MEMCACHED_SERVERS → MemcachedBackend
|
|
111
|
+
4. CACHEKIT_FILE_CACHE_DIR → FileBackend
|
|
112
|
+
5. REDIS_URL, or nothing set → RedisBackend (12-factor / localhost default)
|
|
113
|
+
|
|
114
|
+
Setting more than one of the four prefixed selectors (1-4) raises
|
|
115
|
+
``ConfigurationError`` — auto-detection must be unambiguous; pass
|
|
116
|
+
``backend=`` explicitly to override. The non-prefixed ``REDIS_URL`` is only a
|
|
117
|
+
fallback and never counts as a conflict (12-factor convention).
|
|
118
|
+
|
|
119
|
+
CachekitIO/Memcached/File backends are stateless singletons (cached). Redis
|
|
120
|
+
backends are per-request tenant-scoped wrappers (not cached —
|
|
121
|
+
RedisBackendProvider.get_backend() reads the tenant_context ContextVar). For
|
|
122
|
+
single-tenant deployments (default), tenant_context is set to "default".
|
|
112
123
|
"""
|
|
113
124
|
|
|
125
|
+
# Prefixed selectors in priority order. REDIS_URL is the implicit fallback
|
|
126
|
+
# and intentionally excluded so it never triggers a conflict.
|
|
127
|
+
_SELECTORS = (
|
|
128
|
+
("CACHEKIT_API_KEY", "cachekitio"),
|
|
129
|
+
("CACHEKIT_REDIS_URL", "redis"),
|
|
130
|
+
("CACHEKIT_MEMCACHED_SERVERS", "memcached"),
|
|
131
|
+
("CACHEKIT_FILE_CACHE_DIR", "file"),
|
|
132
|
+
)
|
|
133
|
+
|
|
114
134
|
def __init__(self):
|
|
115
|
-
self.
|
|
135
|
+
self._cachekitio_backend = None
|
|
136
|
+
self._redis_provider = None
|
|
137
|
+
self._memcached_backend = None
|
|
138
|
+
self._file_backend = None
|
|
139
|
+
|
|
140
|
+
def _detect(self):
|
|
141
|
+
"""Return the chosen backend key, or None to use the Redis fallback.
|
|
142
|
+
|
|
143
|
+
Raises ConfigurationError if more than one prefixed selector is set.
|
|
144
|
+
"""
|
|
145
|
+
import os
|
|
146
|
+
|
|
147
|
+
matched = [(env_var, key) for env_var, key in self._SELECTORS if os.environ.get(env_var)]
|
|
148
|
+
if len(matched) > 1:
|
|
149
|
+
from cachekit.config.validation import ConfigurationError
|
|
150
|
+
|
|
151
|
+
names = ", ".join(env_var for env_var, _ in matched)
|
|
152
|
+
raise ConfigurationError(
|
|
153
|
+
f"Ambiguous backend auto-detection: multiple selectors set ({names}). "
|
|
154
|
+
"Set exactly one of CACHEKIT_API_KEY / CACHEKIT_REDIS_URL / "
|
|
155
|
+
"CACHEKIT_MEMCACHED_SERVERS / CACHEKIT_FILE_CACHE_DIR, or pass backend= explicitly."
|
|
156
|
+
)
|
|
157
|
+
return matched[0][1] if matched else None
|
|
116
158
|
|
|
117
159
|
def get_backend(self):
|
|
118
|
-
"""Get
|
|
119
|
-
|
|
160
|
+
"""Get backend instance, auto-detected from environment on first call."""
|
|
161
|
+
choice = self._detect()
|
|
162
|
+
|
|
163
|
+
if choice == "cachekitio":
|
|
164
|
+
if self._cachekitio_backend is None:
|
|
165
|
+
from cachekit.backends.cachekitio import CachekitIOBackend
|
|
166
|
+
|
|
167
|
+
self._cachekitio_backend = CachekitIOBackend()
|
|
168
|
+
return self._cachekitio_backend
|
|
169
|
+
|
|
170
|
+
if choice == "memcached":
|
|
171
|
+
if self._memcached_backend is None:
|
|
172
|
+
from cachekit.backends.memcached import MemcachedBackend, MemcachedBackendConfig
|
|
173
|
+
|
|
174
|
+
self._memcached_backend = MemcachedBackend(MemcachedBackendConfig.from_env())
|
|
175
|
+
return self._memcached_backend
|
|
176
|
+
|
|
177
|
+
if choice == "file":
|
|
178
|
+
if self._file_backend is None:
|
|
179
|
+
from cachekit.backends.file import FileBackend, FileBackendConfig
|
|
180
|
+
|
|
181
|
+
self._file_backend = FileBackend(FileBackendConfig.from_env())
|
|
182
|
+
return self._file_backend
|
|
183
|
+
|
|
184
|
+
# choice == "redis" (explicit CACHEKIT_REDIS_URL) or None (REDIS_URL / localhost fallback).
|
|
185
|
+
# Tenant-scoped: call the provider each time so it re-reads tenant_context.
|
|
186
|
+
if self._redis_provider is None:
|
|
120
187
|
from cachekit.backends.redis.config import RedisBackendConfig
|
|
121
188
|
from cachekit.backends.redis.provider import RedisBackendProvider, tenant_context
|
|
122
189
|
|
|
123
190
|
redis_config = RedisBackendConfig.from_env()
|
|
124
|
-
self.
|
|
191
|
+
self._redis_provider = RedisBackendProvider(redis_url=redis_config.redis_url)
|
|
125
192
|
|
|
126
193
|
# Set default tenant for single-tenant mode (if not already set)
|
|
127
194
|
if tenant_context.get() is None:
|
|
128
195
|
tenant_context.set("default")
|
|
129
196
|
|
|
130
|
-
return self.
|
|
197
|
+
return self._redis_provider.get_backend()
|
|
131
198
|
|
|
132
199
|
|
|
133
200
|
__all__ = [
|
|
@@ -327,7 +327,10 @@ class PerRequestRedisBackend:
|
|
|
327
327
|
"""Acquire distributed lock (LockableBackend protocol).
|
|
328
328
|
|
|
329
329
|
Args:
|
|
330
|
-
key:
|
|
330
|
+
key: Bare cache key (will be tenant-scoped and ``:lock``-suffixed internally).
|
|
331
|
+
The LockableBackend protocol passes the same key as ``get``/``set``/``delete``;
|
|
332
|
+
the ``:lock`` namespace is a Redis-backend implementation detail kept
|
|
333
|
+
on-wire for zero-migration compatibility with existing deployments.
|
|
331
334
|
timeout: How long to hold lock (seconds) before auto-release
|
|
332
335
|
blocking_timeout: Max time to wait for lock (None = non-blocking)
|
|
333
336
|
|
|
@@ -343,7 +346,11 @@ class PerRequestRedisBackend:
|
|
|
343
346
|
"""
|
|
344
347
|
import asyncio
|
|
345
348
|
|
|
346
|
-
|
|
349
|
+
# Derive the on-wire Redis lock name from the bare cache key: ``<scoped_key>:lock``.
|
|
350
|
+
# Keeping this suffix on the wire preserves compatibility with existing Redis
|
|
351
|
+
# deployments — the lock identity didn't change, only the protocol boundary
|
|
352
|
+
# (the wrapper no longer pollutes the cache_key passed in).
|
|
353
|
+
scoped_key = f"{self._scoped_key(key)}:lock"
|
|
347
354
|
lock = None
|
|
348
355
|
try:
|
|
349
356
|
from redis.lock import Lock
|