CacheToolsUtils 10.3__tar.gz → 11.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.
- {cachetoolsutils-10.3 → cachetoolsutils-11.0}/CacheToolsUtils.egg-info/PKG-INFO +43 -19
- {cachetoolsutils-10.3 → cachetoolsutils-11.0}/CacheToolsUtils.egg-info/requires.txt +3 -7
- {cachetoolsutils-10.3 → cachetoolsutils-11.0}/CacheToolsUtils.py +130 -93
- {cachetoolsutils-10.3 → cachetoolsutils-11.0}/PKG-INFO +43 -19
- {cachetoolsutils-10.3 → cachetoolsutils-11.0}/README.md +37 -9
- {cachetoolsutils-10.3 → cachetoolsutils-11.0}/pyproject.toml +10 -5
- {cachetoolsutils-10.3 → cachetoolsutils-11.0}/CacheToolsUtils.egg-info/SOURCES.txt +0 -0
- {cachetoolsutils-10.3 → cachetoolsutils-11.0}/CacheToolsUtils.egg-info/dependency_links.txt +0 -0
- {cachetoolsutils-10.3 → cachetoolsutils-11.0}/CacheToolsUtils.egg-info/top_level.txt +0 -0
- {cachetoolsutils-10.3 → cachetoolsutils-11.0}/LICENSE +0 -0
- {cachetoolsutils-10.3 → cachetoolsutils-11.0}/MANIFEST.in +0 -0
- {cachetoolsutils-10.3 → cachetoolsutils-11.0}/setup.cfg +0 -0
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: CacheToolsUtils
|
|
3
|
-
Version:
|
|
3
|
+
Version: 11.0
|
|
4
4
|
Summary: Cachetools Utilities
|
|
5
5
|
Author-email: Fabien Coelho <cachetools.utils@coelho.net>
|
|
6
|
-
License: CC0
|
|
6
|
+
License-Expression: CC0-1.0
|
|
7
7
|
Project-URL: repository, https://github.com/zx80/cachetools-utils
|
|
8
8
|
Project-URL: documentation, https://zx80.github.io/cachetools-utils/
|
|
9
9
|
Project-URL: issues, https://github.com/zx80/cachetools-utils/issues
|
|
10
10
|
Project-URL: package, https://pypi.org/project/CacheToolsUtils/
|
|
11
11
|
Classifier: Development Status :: 5 - Production/Stable
|
|
12
12
|
Classifier: Intended Audience :: Developers
|
|
13
|
-
Classifier: License :: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
|
|
14
13
|
Classifier: Programming Language :: Python
|
|
15
14
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
15
|
Requires-Python: >=3.10
|
|
@@ -26,14 +25,10 @@ Requires-Dist: ruff; extra == "dev"
|
|
|
26
25
|
Requires-Dist: pytest; extra == "dev"
|
|
27
26
|
Requires-Dist: coverage; extra == "dev"
|
|
28
27
|
Requires-Dist: pymarkdownlnt; extra == "dev"
|
|
29
|
-
Requires-Dist: types-redis; extra == "dev"
|
|
30
|
-
Requires-Dist: types-cachetools; extra == "dev"
|
|
31
28
|
Provides-Extra: doc
|
|
32
|
-
Requires-Dist:
|
|
33
|
-
Requires-Dist:
|
|
34
|
-
Requires-Dist:
|
|
35
|
-
Requires-Dist: sphinx-lint; extra == "doc"
|
|
36
|
-
Requires-Dist: myst_parser; extra == "doc"
|
|
29
|
+
Requires-Dist: mkdocs; extra == "doc"
|
|
30
|
+
Requires-Dist: mkdocs-material; extra == "doc"
|
|
31
|
+
Requires-Dist: mkdocstrings[python]; extra == "doc"
|
|
37
32
|
Provides-Extra: crypt
|
|
38
33
|
Requires-Dist: pycryptodome; extra == "crypt"
|
|
39
34
|
Provides-Extra: tests
|
|
@@ -43,17 +38,16 @@ Provides-Extra: pub
|
|
|
43
38
|
Requires-Dist: build; extra == "pub"
|
|
44
39
|
Requires-Dist: wheel; extra == "pub"
|
|
45
40
|
Requires-Dist: twine; extra == "pub"
|
|
41
|
+
Dynamic: license-file
|
|
46
42
|
|
|
47
43
|
# CacheToolsUtils
|
|
48
44
|
|
|
49
|
-
|
|
50
|
-
[cachetools](https://pypi.org/project/cachetools/)
|
|
51
|
-
[redis](https://redis.io/) and
|
|
52
|
-
[memcached](https://memcached.org/) as storage backends,
|
|
53
|
-
and other cache-related utils.
|
|
45
|
+
Stackable cache classes for sharing, encryption, statistics _and more_
|
|
46
|
+
on top of [cachetools](https://pypi.org/project/cachetools/),
|
|
47
|
+
[redis](https://redis.io/) and [memcached](https://memcached.org/).
|
|
54
48
|
|
|
55
|
-

|
|
50
|
+

|
|
57
51
|

|
|
58
52
|

|
|
59
53
|

|
|
@@ -66,7 +60,37 @@ and other cache-related utils.
|
|
|
66
60
|
For our purpose, a cache is a key-value store, aka a dictionary, possibly with
|
|
67
61
|
some constraints on keys (type, size) and values (size, serialization).
|
|
68
62
|
This module provides new caches, wrappers and other utilities suitable to use
|
|
69
|
-
with `cachetools`.
|
|
63
|
+
with `cachetools`. Example:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
import redis
|
|
67
|
+
import CacheToolsUtils as ctu
|
|
68
|
+
import config
|
|
69
|
+
|
|
70
|
+
redis_storage = redis.Redis(**config.REDIS_SERVER)
|
|
71
|
+
storage = ctu.RedisCache(redis_storage, raw=True, ttl=120)
|
|
72
|
+
crypto = ctu.EncryptedCache(storage, config.CACHE_KEY)
|
|
73
|
+
cache = ctu.ToBytesCache(crypto)
|
|
74
|
+
|
|
75
|
+
@ctu.cached(ctu.AutoPrefixedCache(cache), key=ctu.json_key)
|
|
76
|
+
def repeat(s: str, n: int) -> str:
|
|
77
|
+
return s * n
|
|
78
|
+
|
|
79
|
+
@ctu.cached(ctu.AutoPrefixedCache(cache), key=ctu.json_key)
|
|
80
|
+
def banged(s: str, n: int) -> str:
|
|
81
|
+
return repeat(s, n) + "!"
|
|
82
|
+
|
|
83
|
+
@ctu.cached(ctu.AutoPrefixedCache(cache), key=ctu.json_key)
|
|
84
|
+
def question(s: str, n: int) -> str:
|
|
85
|
+
return repeat(s, n) + "?"
|
|
86
|
+
|
|
87
|
+
print(banged("aa", 3)) # add 2 cache entries
|
|
88
|
+
print(question("aa", 3)) # add 1 entry, 1 hit
|
|
89
|
+
print(repeat("aa", 3)) # cache hit!
|
|
90
|
+
print(banged("aa", 3)) # cache hit!
|
|
91
|
+
|
|
92
|
+
assert cache.hits() > 0
|
|
93
|
+
```
|
|
70
94
|
|
|
71
95
|
### Cache classes
|
|
72
96
|
|
|
@@ -12,15 +12,11 @@ ruff
|
|
|
12
12
|
pytest
|
|
13
13
|
coverage
|
|
14
14
|
pymarkdownlnt
|
|
15
|
-
types-redis
|
|
16
|
-
types-cachetools
|
|
17
15
|
|
|
18
16
|
[doc]
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
sphinx-lint
|
|
23
|
-
myst_parser
|
|
17
|
+
mkdocs
|
|
18
|
+
mkdocs-material
|
|
19
|
+
mkdocstrings[python]
|
|
24
20
|
|
|
25
21
|
[pub]
|
|
26
22
|
build
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
CacheTools Utilities. This code is public domain.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
# FIXME 3.13t checks without Cipher package
|
|
6
|
+
# pyright: reportMissingImports=false
|
|
7
|
+
|
|
5
8
|
from typing import Any, Callable, MutableMapping
|
|
6
9
|
import abc
|
|
7
10
|
|
|
@@ -25,7 +28,7 @@ _NO_DEFAULT = object()
|
|
|
25
28
|
|
|
26
29
|
|
|
27
30
|
class _MutMapMix:
|
|
28
|
-
"""Convenient MutableMapping Mixin, forward to _cache
|
|
31
|
+
"""Convenient MutableMapping Mixin, forward to `_cache`."""
|
|
29
32
|
|
|
30
33
|
# FIXME coverage reports this as missing with Python 3.14
|
|
31
34
|
_cache: MutableMapping # pragma: no cover
|
|
@@ -50,7 +53,7 @@ class _MutMapMix:
|
|
|
50
53
|
|
|
51
54
|
|
|
52
55
|
class _KeyMutMapMix(_MutMapMix):
|
|
53
|
-
"""Convenient MutableMapping Mixin with a key filter, forward to _cache
|
|
56
|
+
"""Convenient MutableMapping Mixin with a key filter, forward to `_cache`."""
|
|
54
57
|
|
|
55
58
|
@abc.abstractmethod
|
|
56
59
|
def _key(self, key: Any) -> Any: # pragma: no cover
|
|
@@ -70,7 +73,7 @@ class _KeyMutMapMix(_MutMapMix):
|
|
|
70
73
|
|
|
71
74
|
|
|
72
75
|
class _StatsMix:
|
|
73
|
-
"""Convenient Mixin to forward stats methods to _cache
|
|
76
|
+
"""Convenient Mixin to forward stats methods to `_cache`."""
|
|
74
77
|
|
|
75
78
|
def hits(self) -> float|None:
|
|
76
79
|
return self._cache.hits() # type: ignore
|
|
@@ -111,9 +114,11 @@ class _RedisMix: # pragma: no cover
|
|
|
111
114
|
class DebugCache(_StatsMix, MutableMapping):
|
|
112
115
|
"""Debug class.
|
|
113
116
|
|
|
114
|
-
:
|
|
115
|
-
|
|
116
|
-
|
|
117
|
+
Parameters:
|
|
118
|
+
|
|
119
|
+
- cache: actual cache
|
|
120
|
+
- log: logging instance
|
|
121
|
+
- name: name of instance for output
|
|
117
122
|
"""
|
|
118
123
|
|
|
119
124
|
def __init__(self, cache: MutableMapping, log: logging.Logger, name="cache"):
|
|
@@ -159,7 +164,7 @@ class DebugCache(_StatsMix, MutableMapping):
|
|
|
159
164
|
|
|
160
165
|
|
|
161
166
|
class DictCache(_MutMapMix, MutableMapping):
|
|
162
|
-
"""Cache class based on dict
|
|
167
|
+
"""Cache class based on `dict`."""
|
|
163
168
|
|
|
164
169
|
def __init__(self):
|
|
165
170
|
self._cache = dict()
|
|
@@ -171,17 +176,19 @@ class DictCache(_MutMapMix, MutableMapping):
|
|
|
171
176
|
class LockedCache(_MutMapMix, _StatsMix, _RedisMix, MutableMapping):
|
|
172
177
|
"""Cache class with a lock.
|
|
173
178
|
|
|
174
|
-
:
|
|
175
|
-
:param lock: lock (context manager) to use
|
|
179
|
+
Parameters:
|
|
176
180
|
|
|
177
|
-
|
|
181
|
+
- cache: actual cache.
|
|
182
|
+
- lock: lock (context manager) to use
|
|
178
183
|
|
|
179
|
-
|
|
184
|
+
The locked layer should be the last one before the actual cache.
|
|
180
185
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
186
|
+
```python
|
|
187
|
+
import threading
|
|
188
|
+
import cachetools as ct
|
|
189
|
+
import CacheToolsUtils as ctu
|
|
190
|
+
cache = ctu.LockedCache(ct.LFUCache(), threading.RLock())
|
|
191
|
+
```
|
|
185
192
|
"""
|
|
186
193
|
|
|
187
194
|
def __init__(self, cache: MutableMapping, lock):
|
|
@@ -208,8 +215,10 @@ class LockedCache(_MutMapMix, _StatsMix, _RedisMix, MutableMapping):
|
|
|
208
215
|
class PrefixedCache(_KeyMutMapMix, _StatsMix, MutableMapping):
|
|
209
216
|
"""Cache class to add a key prefix.
|
|
210
217
|
|
|
211
|
-
:
|
|
212
|
-
|
|
218
|
+
Parameters:
|
|
219
|
+
|
|
220
|
+
- cache: actual cache.
|
|
221
|
+
- prefix: prefix to prepend to keys.
|
|
213
222
|
"""
|
|
214
223
|
|
|
215
224
|
def __init__(self, cache: MutableMapping, prefix: str|bytes = ""):
|
|
@@ -229,9 +238,11 @@ class PrefixedCache(_KeyMutMapMix, _StatsMix, MutableMapping):
|
|
|
229
238
|
class AutoPrefixedCache(PrefixedCache):
|
|
230
239
|
"""Cache class with an automatic counter-based string prefix.
|
|
231
240
|
|
|
232
|
-
:
|
|
233
|
-
|
|
234
|
-
|
|
241
|
+
Parameters:
|
|
242
|
+
|
|
243
|
+
- cache: actual cache.
|
|
244
|
+
- sep: prefix separator, default is ".".
|
|
245
|
+
- method: encoding method in "b64", "b64u", "b32", "b32x", "b16",
|
|
235
246
|
"a85" and "b85". Default is "b64".
|
|
236
247
|
"""
|
|
237
248
|
|
|
@@ -262,16 +273,17 @@ class AutoPrefixedCache(PrefixedCache):
|
|
|
262
273
|
class StatsCache(_MutMapMix, MutableMapping):
|
|
263
274
|
"""Cache class to keep stats.
|
|
264
275
|
|
|
265
|
-
:
|
|
276
|
+
Parameter:
|
|
266
277
|
|
|
267
|
-
|
|
278
|
+
- cache: actual cache.
|
|
268
279
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
280
|
+
```python
|
|
281
|
+
import cachetools as ct
|
|
282
|
+
import CacheToolsUtils as ctu
|
|
283
|
+
cache = ctu.StatsCache(ct.LRUCache())
|
|
284
|
+
```
|
|
273
285
|
|
|
274
|
-
Note that CacheTools
|
|
286
|
+
Note that CacheTools `cached` decorator with `info=True` provides
|
|
275
287
|
hits, misses, maxsize and currsize information.
|
|
276
288
|
However, this only works for its own classes.
|
|
277
289
|
"""
|
|
@@ -286,19 +298,25 @@ class StatsCache(_MutMapMix, MutableMapping):
|
|
|
286
298
|
|
|
287
299
|
def reset(self):
|
|
288
300
|
"""Reset internal stats data."""
|
|
289
|
-
self._reads, self._writes, self._dels, self._hits = 0, 0, 0, 0
|
|
301
|
+
self._tests, self._reads, self._writes, self._dels, self._hits = 0, 0, 0, 0, 0
|
|
290
302
|
|
|
291
303
|
def stats(self) -> dict[str, Any]:
|
|
292
304
|
"""Return available stats data as dict."""
|
|
293
305
|
return {
|
|
294
306
|
"type": 1,
|
|
307
|
+
"tests": self._tests,
|
|
295
308
|
"reads": self._reads,
|
|
296
309
|
"writes": self._writes,
|
|
297
310
|
"dels": self._dels,
|
|
298
|
-
"hits": self.
|
|
311
|
+
"hits": self._hits,
|
|
312
|
+
"hit_rate": self.hits(),
|
|
299
313
|
"size": self._cache.__len__()
|
|
300
314
|
}
|
|
301
315
|
|
|
316
|
+
def __contains__(self, key):
|
|
317
|
+
self._tests += 1
|
|
318
|
+
return self._cache.__contains__(key)
|
|
319
|
+
|
|
302
320
|
def __getitem__(self, key):
|
|
303
321
|
self._reads += 1
|
|
304
322
|
res = self._cache.__getitem__(key)
|
|
@@ -320,9 +338,11 @@ class StatsCache(_MutMapMix, MutableMapping):
|
|
|
320
338
|
class TwoLevelCache(_MutMapMix, MutableMapping):
|
|
321
339
|
"""Two-Level Cache class for CacheTools.
|
|
322
340
|
|
|
323
|
-
:
|
|
324
|
-
|
|
325
|
-
|
|
341
|
+
Parameters:
|
|
342
|
+
|
|
343
|
+
- cache: first (smaller, shorter TTL) cache
|
|
344
|
+
- cache2: second (larger, longer TTL) cache
|
|
345
|
+
- resilient: whether to ignore cache2 failures
|
|
326
346
|
"""
|
|
327
347
|
|
|
328
348
|
def __init__(self, cache: MutableMapping, cache2: MutableMapping, resilient=False):
|
|
@@ -448,10 +468,12 @@ class _Cipher:
|
|
|
448
468
|
class EncryptedCache(_KeyMutMapMix, _StatsMix, MutableMapping):
|
|
449
469
|
"""Encrypted Bytes Key-Value Cache.
|
|
450
470
|
|
|
451
|
-
:
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
471
|
+
Parameters:
|
|
472
|
+
|
|
473
|
+
- secret: bytes of secret, at least 16 bytes.
|
|
474
|
+
- hsize: size of hashed key, default is *16*.
|
|
475
|
+
- csize: value checksum size, default is *0*.
|
|
476
|
+
- cipher: chose cipher from "Salsa20", "AES-128-CBC" or "ChaCha20".
|
|
455
477
|
|
|
456
478
|
The key is *not* encrypted but simply hashed, thus they are
|
|
457
479
|
fixed size with a very low collision probability.
|
|
@@ -544,14 +566,16 @@ class ToBytesCache(_KeyMutMapMix, _StatsMix, MutableMapping):
|
|
|
544
566
|
def cached(cache, *args, **kwargs):
|
|
545
567
|
"""Extended decorator with delete and exists.
|
|
546
568
|
|
|
547
|
-
All parameters are forwarded to
|
|
569
|
+
All parameters are forwarded to `cachetools.cached`.
|
|
570
|
+
|
|
571
|
+
Parameter:
|
|
548
572
|
|
|
549
|
-
|
|
573
|
+
- cache: actual cache.
|
|
550
574
|
|
|
551
|
-
If *f(\\*args, \\*\\*kwargs)* is the
|
|
575
|
+
If *f(\\*args, \\*\\*kwargs)* is the _cached_ function, then:
|
|
552
576
|
|
|
553
|
-
-
|
|
554
|
-
-
|
|
577
|
+
- `f.cache_in(*args, **kwargs)` tells whether the result is cached.
|
|
578
|
+
- `f.cache_del(*args, **kwargs)` deletes (invalidates) the cached result.
|
|
555
579
|
"""
|
|
556
580
|
|
|
557
581
|
def decorate(fun: Callable):
|
|
@@ -594,15 +618,17 @@ def cacheMethods(
|
|
|
594
618
|
):
|
|
595
619
|
"""Cache some object methods.
|
|
596
620
|
|
|
597
|
-
:
|
|
598
|
-
:param obj: object instance to be cached.
|
|
599
|
-
:param gen: generator of PrefixedCache.
|
|
600
|
-
:param opts: additional parameters when calling `cached`.
|
|
601
|
-
:param funs: name of methods and corresponding prefix
|
|
621
|
+
Parameters:
|
|
602
622
|
|
|
603
|
-
|
|
623
|
+
- cache: cache to use.
|
|
624
|
+
- obj: object instance to be cached.
|
|
625
|
+
- gen: generator of PrefixedCache.
|
|
626
|
+
- opts: additional parameters when calling `cached`.
|
|
627
|
+
- funs: name of methods and corresponding prefix
|
|
604
628
|
|
|
605
|
-
|
|
629
|
+
```python
|
|
630
|
+
cacheMethods(cache, item, PrefixedCache, compute1="c1.", compute2="c2.")
|
|
631
|
+
```
|
|
606
632
|
"""
|
|
607
633
|
for fun, prefix in funs.items():
|
|
608
634
|
assert hasattr(obj, fun), f"cannot cache missing method {fun} on {obj}"
|
|
@@ -621,15 +647,17 @@ def cacheFunctions(
|
|
|
621
647
|
):
|
|
622
648
|
"""Cache some global functions, with a prefix.
|
|
623
649
|
|
|
624
|
-
:
|
|
625
|
-
:param globs: global object dictionary.
|
|
626
|
-
:param gen: generator of PrefixedCache.
|
|
627
|
-
:param opts: additional parameters when calling `cached`.
|
|
628
|
-
:param funs: name of functions and corresponding prefix
|
|
650
|
+
Parameters:
|
|
629
651
|
|
|
630
|
-
|
|
652
|
+
- cache: cache to use.
|
|
653
|
+
- globs: global object dictionary.
|
|
654
|
+
- gen: generator of PrefixedCache.
|
|
655
|
+
- opts: additional parameters when calling `cached`.
|
|
656
|
+
- funs: name of functions and corresponding prefix
|
|
631
657
|
|
|
632
|
-
|
|
658
|
+
```python
|
|
659
|
+
cacheFunctions(cache, globals(), PrefixedCache, fun1="f1.", fun2="f2.")
|
|
660
|
+
```
|
|
633
661
|
"""
|
|
634
662
|
for fun, prefix in funs.items():
|
|
635
663
|
assert fun in globs, "caching functions: {fun} not found"
|
|
@@ -688,13 +716,13 @@ def full_hash_key(*args, **kwargs) -> str:
|
|
|
688
716
|
# MEMCACHED
|
|
689
717
|
#
|
|
690
718
|
class JsonSerde:
|
|
691
|
-
"""JSON serialize/deserialize class for MemCached (
|
|
719
|
+
"""JSON serialize/deserialize class for MemCached (`pymemcache`).
|
|
692
720
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
721
|
+
```python
|
|
722
|
+
import pymemcache as pmc
|
|
723
|
+
import CacheToolsUtils as ctu
|
|
724
|
+
pmc_cache = pmc.Client(server="localhost", serde=ctu.JsonSerde())
|
|
725
|
+
```
|
|
698
726
|
"""
|
|
699
727
|
|
|
700
728
|
# keep strings, else json
|
|
@@ -721,16 +749,19 @@ class JsonSerde:
|
|
|
721
749
|
class MemCached(_KeyMutMapMix, MutableMapping):
|
|
722
750
|
"""MemCached-compatible wrapper class for cachetools with key encoding.
|
|
723
751
|
|
|
724
|
-
:
|
|
752
|
+
Parameter:
|
|
725
753
|
|
|
726
|
-
|
|
754
|
+
- cache: actual memcached cache.
|
|
727
755
|
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
756
|
+
```python
|
|
757
|
+
import pymemcache as pmc
|
|
758
|
+
import CacheToolsUtils as ctu
|
|
759
|
+
cache = ctu.MemCached(pmc.Client(server="localhost", serde=ctu.JsonSerde()))
|
|
731
760
|
|
|
732
|
-
|
|
733
|
-
|
|
761
|
+
@ctu.cached(cache=cache)
|
|
762
|
+
def whatever(...):
|
|
763
|
+
...
|
|
764
|
+
```
|
|
734
765
|
"""
|
|
735
766
|
|
|
736
767
|
def __init__(self, cache):
|
|
@@ -764,15 +795,17 @@ class MemCached(_KeyMutMapMix, MutableMapping):
|
|
|
764
795
|
class PrefixedMemCached(MemCached):
|
|
765
796
|
"""MemCached-compatible wrapper class for cachetools with a key prefix.
|
|
766
797
|
|
|
767
|
-
:
|
|
768
|
-
:param prefix: post key-encoding prepended prefix.
|
|
798
|
+
Parameters:
|
|
769
799
|
|
|
770
|
-
|
|
800
|
+
- cache: actual memcached cache.
|
|
801
|
+
- prefix: post key-encoding prepended prefix.
|
|
771
802
|
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
803
|
+
```python
|
|
804
|
+
import pymemcache as pmc
|
|
805
|
+
import CacheToolsUtils as ctu
|
|
806
|
+
# add a "app." prefix to keys, after serialization
|
|
807
|
+
cache = ctu.PrefixedMemCached(pmc.Client(server="localhost", serde=ctu.JsonSerde()), "app.")
|
|
808
|
+
```
|
|
776
809
|
"""
|
|
777
810
|
|
|
778
811
|
def __init__(self, cache, prefix: str = ""):
|
|
@@ -787,20 +820,22 @@ class PrefixedMemCached(MemCached):
|
|
|
787
820
|
# REDIS
|
|
788
821
|
#
|
|
789
822
|
class RedisCache(MutableMapping):
|
|
790
|
-
"""Redis TTL-ed wrapper for cachetools (
|
|
823
|
+
"""Redis TTL-ed wrapper for cachetools (`redis`).
|
|
791
824
|
|
|
792
|
-
:
|
|
793
|
-
:param ttl: time-to-live in seconds, used as default expiration (``ex``), default is 600.
|
|
794
|
-
:param raw: whether to serialize keys and values, default is *False*.
|
|
825
|
+
Parameters:
|
|
795
826
|
|
|
796
|
-
|
|
827
|
+
- cache: actual redis cache.
|
|
828
|
+
- ttl: time-to-live in seconds, used as default expiration (`ex`), default is _600_.
|
|
829
|
+
- raw: whether to serialize keys and values, default is *False*.
|
|
797
830
|
|
|
798
|
-
|
|
831
|
+
Keys and values are serialized in *JSON*.
|
|
799
832
|
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
833
|
+
```python
|
|
834
|
+
import redis
|
|
835
|
+
import CacheToolsUtils as ctu
|
|
836
|
+
# redis with 1 hour expiration
|
|
837
|
+
cache = ctu.RedisCache(redis.Redis(host="localhost"), 3600)
|
|
838
|
+
```
|
|
804
839
|
"""
|
|
805
840
|
|
|
806
841
|
def __init__(self, cache, ttl=600, raw=False):
|
|
@@ -880,16 +915,18 @@ class RedisCache(MutableMapping):
|
|
|
880
915
|
class PrefixedRedisCache(RedisCache):
|
|
881
916
|
"""Prefixed Redis wrapper class for cachetools.
|
|
882
917
|
|
|
883
|
-
:
|
|
884
|
-
:param prefix: post key encoding prefix, default is empty.
|
|
885
|
-
:param ttl: time-to-live in seconds, used as default expiration (``ex``), default is 600.
|
|
918
|
+
Parameters:
|
|
886
919
|
|
|
887
|
-
|
|
920
|
+
- cache: actual redis cache.
|
|
921
|
+
- prefix: post key encoding prefix, default is empty.
|
|
922
|
+
- ttl: time-to-live in seconds, used as default expiration (`ex`), default is _600_.
|
|
888
923
|
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
924
|
+
```python
|
|
925
|
+
import redis
|
|
926
|
+
import CacheToolsUtils as ctu
|
|
927
|
+
# redis with "app." prefix and 1 hour expiration
|
|
928
|
+
cache = ctu.PrefixedRedisCache(redis.Redis(host="localhost"), "app.", 3600)
|
|
929
|
+
```
|
|
893
930
|
"""
|
|
894
931
|
|
|
895
932
|
def __init__(self, cache, prefix: str = "", ttl=600):
|
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: CacheToolsUtils
|
|
3
|
-
Version:
|
|
3
|
+
Version: 11.0
|
|
4
4
|
Summary: Cachetools Utilities
|
|
5
5
|
Author-email: Fabien Coelho <cachetools.utils@coelho.net>
|
|
6
|
-
License: CC0
|
|
6
|
+
License-Expression: CC0-1.0
|
|
7
7
|
Project-URL: repository, https://github.com/zx80/cachetools-utils
|
|
8
8
|
Project-URL: documentation, https://zx80.github.io/cachetools-utils/
|
|
9
9
|
Project-URL: issues, https://github.com/zx80/cachetools-utils/issues
|
|
10
10
|
Project-URL: package, https://pypi.org/project/CacheToolsUtils/
|
|
11
11
|
Classifier: Development Status :: 5 - Production/Stable
|
|
12
12
|
Classifier: Intended Audience :: Developers
|
|
13
|
-
Classifier: License :: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
|
|
14
13
|
Classifier: Programming Language :: Python
|
|
15
14
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
15
|
Requires-Python: >=3.10
|
|
@@ -26,14 +25,10 @@ Requires-Dist: ruff; extra == "dev"
|
|
|
26
25
|
Requires-Dist: pytest; extra == "dev"
|
|
27
26
|
Requires-Dist: coverage; extra == "dev"
|
|
28
27
|
Requires-Dist: pymarkdownlnt; extra == "dev"
|
|
29
|
-
Requires-Dist: types-redis; extra == "dev"
|
|
30
|
-
Requires-Dist: types-cachetools; extra == "dev"
|
|
31
28
|
Provides-Extra: doc
|
|
32
|
-
Requires-Dist:
|
|
33
|
-
Requires-Dist:
|
|
34
|
-
Requires-Dist:
|
|
35
|
-
Requires-Dist: sphinx-lint; extra == "doc"
|
|
36
|
-
Requires-Dist: myst_parser; extra == "doc"
|
|
29
|
+
Requires-Dist: mkdocs; extra == "doc"
|
|
30
|
+
Requires-Dist: mkdocs-material; extra == "doc"
|
|
31
|
+
Requires-Dist: mkdocstrings[python]; extra == "doc"
|
|
37
32
|
Provides-Extra: crypt
|
|
38
33
|
Requires-Dist: pycryptodome; extra == "crypt"
|
|
39
34
|
Provides-Extra: tests
|
|
@@ -43,17 +38,16 @@ Provides-Extra: pub
|
|
|
43
38
|
Requires-Dist: build; extra == "pub"
|
|
44
39
|
Requires-Dist: wheel; extra == "pub"
|
|
45
40
|
Requires-Dist: twine; extra == "pub"
|
|
41
|
+
Dynamic: license-file
|
|
46
42
|
|
|
47
43
|
# CacheToolsUtils
|
|
48
44
|
|
|
49
|
-
|
|
50
|
-
[cachetools](https://pypi.org/project/cachetools/)
|
|
51
|
-
[redis](https://redis.io/) and
|
|
52
|
-
[memcached](https://memcached.org/) as storage backends,
|
|
53
|
-
and other cache-related utils.
|
|
45
|
+
Stackable cache classes for sharing, encryption, statistics _and more_
|
|
46
|
+
on top of [cachetools](https://pypi.org/project/cachetools/),
|
|
47
|
+
[redis](https://redis.io/) and [memcached](https://memcached.org/).
|
|
54
48
|
|
|
55
|
-

|
|
50
|
+

|
|
57
51
|

|
|
58
52
|

|
|
59
53
|

|
|
@@ -66,7 +60,37 @@ and other cache-related utils.
|
|
|
66
60
|
For our purpose, a cache is a key-value store, aka a dictionary, possibly with
|
|
67
61
|
some constraints on keys (type, size) and values (size, serialization).
|
|
68
62
|
This module provides new caches, wrappers and other utilities suitable to use
|
|
69
|
-
with `cachetools`.
|
|
63
|
+
with `cachetools`. Example:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
import redis
|
|
67
|
+
import CacheToolsUtils as ctu
|
|
68
|
+
import config
|
|
69
|
+
|
|
70
|
+
redis_storage = redis.Redis(**config.REDIS_SERVER)
|
|
71
|
+
storage = ctu.RedisCache(redis_storage, raw=True, ttl=120)
|
|
72
|
+
crypto = ctu.EncryptedCache(storage, config.CACHE_KEY)
|
|
73
|
+
cache = ctu.ToBytesCache(crypto)
|
|
74
|
+
|
|
75
|
+
@ctu.cached(ctu.AutoPrefixedCache(cache), key=ctu.json_key)
|
|
76
|
+
def repeat(s: str, n: int) -> str:
|
|
77
|
+
return s * n
|
|
78
|
+
|
|
79
|
+
@ctu.cached(ctu.AutoPrefixedCache(cache), key=ctu.json_key)
|
|
80
|
+
def banged(s: str, n: int) -> str:
|
|
81
|
+
return repeat(s, n) + "!"
|
|
82
|
+
|
|
83
|
+
@ctu.cached(ctu.AutoPrefixedCache(cache), key=ctu.json_key)
|
|
84
|
+
def question(s: str, n: int) -> str:
|
|
85
|
+
return repeat(s, n) + "?"
|
|
86
|
+
|
|
87
|
+
print(banged("aa", 3)) # add 2 cache entries
|
|
88
|
+
print(question("aa", 3)) # add 1 entry, 1 hit
|
|
89
|
+
print(repeat("aa", 3)) # cache hit!
|
|
90
|
+
print(banged("aa", 3)) # cache hit!
|
|
91
|
+
|
|
92
|
+
assert cache.hits() > 0
|
|
93
|
+
```
|
|
70
94
|
|
|
71
95
|
### Cache classes
|
|
72
96
|
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
# CacheToolsUtils
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[cachetools](https://pypi.org/project/cachetools/)
|
|
5
|
-
[redis](https://redis.io/) and
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-

|
|
10
|
-

|
|
3
|
+
Stackable cache classes for sharing, encryption, statistics _and more_
|
|
4
|
+
on top of [cachetools](https://pypi.org/project/cachetools/),
|
|
5
|
+
[redis](https://redis.io/) and [memcached](https://memcached.org/).
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+

|
|
11
9
|

|
|
12
10
|

|
|
13
11
|

|
|
@@ -20,7 +18,37 @@ and other cache-related utils.
|
|
|
20
18
|
For our purpose, a cache is a key-value store, aka a dictionary, possibly with
|
|
21
19
|
some constraints on keys (type, size) and values (size, serialization).
|
|
22
20
|
This module provides new caches, wrappers and other utilities suitable to use
|
|
23
|
-
with `cachetools`.
|
|
21
|
+
with `cachetools`. Example:
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
import redis
|
|
25
|
+
import CacheToolsUtils as ctu
|
|
26
|
+
import config
|
|
27
|
+
|
|
28
|
+
redis_storage = redis.Redis(**config.REDIS_SERVER)
|
|
29
|
+
storage = ctu.RedisCache(redis_storage, raw=True, ttl=120)
|
|
30
|
+
crypto = ctu.EncryptedCache(storage, config.CACHE_KEY)
|
|
31
|
+
cache = ctu.ToBytesCache(crypto)
|
|
32
|
+
|
|
33
|
+
@ctu.cached(ctu.AutoPrefixedCache(cache), key=ctu.json_key)
|
|
34
|
+
def repeat(s: str, n: int) -> str:
|
|
35
|
+
return s * n
|
|
36
|
+
|
|
37
|
+
@ctu.cached(ctu.AutoPrefixedCache(cache), key=ctu.json_key)
|
|
38
|
+
def banged(s: str, n: int) -> str:
|
|
39
|
+
return repeat(s, n) + "!"
|
|
40
|
+
|
|
41
|
+
@ctu.cached(ctu.AutoPrefixedCache(cache), key=ctu.json_key)
|
|
42
|
+
def question(s: str, n: int) -> str:
|
|
43
|
+
return repeat(s, n) + "?"
|
|
44
|
+
|
|
45
|
+
print(banged("aa", 3)) # add 2 cache entries
|
|
46
|
+
print(question("aa", 3)) # add 1 entry, 1 hit
|
|
47
|
+
print(repeat("aa", 3)) # cache hit!
|
|
48
|
+
print(banged("aa", 3)) # cache hit!
|
|
49
|
+
|
|
50
|
+
assert cache.hits() > 0
|
|
51
|
+
```
|
|
24
52
|
|
|
25
53
|
### Cache classes
|
|
26
54
|
|
|
@@ -4,17 +4,16 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "CacheToolsUtils"
|
|
7
|
-
version = "
|
|
7
|
+
version = "11.0"
|
|
8
8
|
authors = [ { name = "Fabien Coelho", email = "cachetools.utils@coelho.net" } ]
|
|
9
9
|
description = "Cachetools Utilities"
|
|
10
10
|
readme = "README.md"
|
|
11
|
-
license =
|
|
11
|
+
license = "CC0-1.0"
|
|
12
12
|
requires-python = ">= 3.10"
|
|
13
13
|
dependencies = [ "cachetools" ]
|
|
14
14
|
classifiers = [
|
|
15
15
|
"Development Status :: 5 - Production/Stable",
|
|
16
16
|
"Intended Audience :: Developers",
|
|
17
|
-
"License :: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication",
|
|
18
17
|
"Programming Language :: Python",
|
|
19
18
|
"Topic :: Software Development :: Libraries :: Python Modules"
|
|
20
19
|
]
|
|
@@ -30,8 +29,14 @@ package = "https://pypi.org/project/CacheToolsUtils/"
|
|
|
30
29
|
py-modules = [ "CacheToolsUtils" ]
|
|
31
30
|
|
|
32
31
|
[project.optional-dependencies]
|
|
33
|
-
dev = [
|
|
34
|
-
|
|
32
|
+
dev = [
|
|
33
|
+
"mypy", "pyright",
|
|
34
|
+
# "types-redis", "types-cachetools",
|
|
35
|
+
"black", "flake8", "ruff",
|
|
36
|
+
"pytest", "coverage",
|
|
37
|
+
"pymarkdownlnt",
|
|
38
|
+
]
|
|
39
|
+
doc = [ "mkdocs", "mkdocs-material", "mkdocstrings[python]" ]
|
|
35
40
|
crypt = [ "pycryptodome" ]
|
|
36
41
|
tests = [ "pymemcache", "redis" ]
|
|
37
42
|
pub = [ "build", "wheel", "twine" ]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|