CacheToolsUtils 10.1__tar.gz → 10.3__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.1 → cachetoolsutils-10.3}/CacheToolsUtils.egg-info/PKG-INFO +4 -3
- {cachetoolsutils-10.1 → cachetoolsutils-10.3}/CacheToolsUtils.py +93 -17
- {cachetoolsutils-10.1 → cachetoolsutils-10.3}/PKG-INFO +4 -3
- {cachetoolsutils-10.1 → cachetoolsutils-10.3}/README.md +2 -1
- {cachetoolsutils-10.1 → cachetoolsutils-10.3}/pyproject.toml +1 -1
- {cachetoolsutils-10.1 → cachetoolsutils-10.3}/CacheToolsUtils.egg-info/SOURCES.txt +0 -0
- {cachetoolsutils-10.1 → cachetoolsutils-10.3}/CacheToolsUtils.egg-info/dependency_links.txt +0 -0
- {cachetoolsutils-10.1 → cachetoolsutils-10.3}/CacheToolsUtils.egg-info/requires.txt +0 -0
- {cachetoolsutils-10.1 → cachetoolsutils-10.3}/CacheToolsUtils.egg-info/top_level.txt +0 -0
- {cachetoolsutils-10.1 → cachetoolsutils-10.3}/LICENSE +0 -0
- {cachetoolsutils-10.1 → cachetoolsutils-10.3}/MANIFEST.in +0 -0
- {cachetoolsutils-10.1 → cachetoolsutils-10.3}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: CacheToolsUtils
|
|
3
|
-
Version: 10.
|
|
3
|
+
Version: 10.3
|
|
4
4
|
Summary: Cachetools Utilities
|
|
5
5
|
Author-email: Fabien Coelho <cachetools.utils@coelho.net>
|
|
6
6
|
License: CC0
|
|
@@ -53,7 +53,7 @@ Classes to add key prefix and stats to
|
|
|
53
53
|
and other cache-related utils.
|
|
54
54
|
|
|
55
55
|

|
|
56
|
-

|
|
57
57
|

|
|
58
58
|

|
|
59
59
|

|
|
@@ -81,6 +81,7 @@ with `cachetools`.
|
|
|
81
81
|
|
|
82
82
|
- `PrefixedCache`, `PrefixedMemCached` and `PrefixedRedisCache` add a prefix to
|
|
83
83
|
distinguish sources on a shared cache.
|
|
84
|
+
- `AutoPrefixedCache` add a counter-based prefix.
|
|
84
85
|
- `StatsCache`, `MemCached` and `RedisCache` add a `hits` method
|
|
85
86
|
to report the cache hit rate, `stats` to report statistics and
|
|
86
87
|
`reset` to reset statistics.
|
|
@@ -226,6 +226,39 @@ class PrefixedCache(_KeyMutMapMix, _StatsMix, MutableMapping):
|
|
|
226
226
|
return self._prefix + self._cast(key) # type: ignore
|
|
227
227
|
|
|
228
228
|
|
|
229
|
+
class AutoPrefixedCache(PrefixedCache):
|
|
230
|
+
"""Cache class with an automatic counter-based string prefix.
|
|
231
|
+
|
|
232
|
+
:param cache: actual cache.
|
|
233
|
+
:param sep: prefix separator, default is ".".
|
|
234
|
+
:param method: encoding method in "b64", "b64u", "b32", "b32x", "b16",
|
|
235
|
+
"a85" and "b85". Default is "b64".
|
|
236
|
+
"""
|
|
237
|
+
|
|
238
|
+
_COUNTER: int = 0
|
|
239
|
+
|
|
240
|
+
_METHODS: dict[str, Callable[[Any], bytes]] = {
|
|
241
|
+
"b64": base64.b64encode,
|
|
242
|
+
"b64u": base64.urlsafe_b64encode,
|
|
243
|
+
"b32": base64.b32encode,
|
|
244
|
+
"b32x": base64.b32hexencode,
|
|
245
|
+
"b16": base64.b16encode,
|
|
246
|
+
"a85": base64.a85encode,
|
|
247
|
+
"b85": base64.b85encode,
|
|
248
|
+
# "z85": base64.z85encode, # Python 3.13
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
def __init__(self, cache: MutableMapping, sep: str = ".", method: str = "b64"):
|
|
252
|
+
if method not in AutoPrefixedCache._METHODS:
|
|
253
|
+
raise Exception(f"invalid conversion method: {method}")
|
|
254
|
+
encoder = AutoPrefixedCache._METHODS[method]
|
|
255
|
+
length = max(1, (AutoPrefixedCache._COUNTER.bit_length() + 7) // 8)
|
|
256
|
+
# NOTE byteorder default added to 3.11
|
|
257
|
+
prefix = encoder(AutoPrefixedCache._COUNTER.to_bytes(length, byteorder="big")).decode("ASCII")
|
|
258
|
+
AutoPrefixedCache._COUNTER += 1
|
|
259
|
+
super().__init__(cache, prefix + sep)
|
|
260
|
+
|
|
261
|
+
|
|
229
262
|
class StatsCache(_MutMapMix, MutableMapping):
|
|
230
263
|
"""Cache class to keep stats.
|
|
231
264
|
|
|
@@ -367,6 +400,48 @@ class TwoLevelCache(_MutMapMix, MutableMapping):
|
|
|
367
400
|
self._cache2.reset() # type: ignore
|
|
368
401
|
|
|
369
402
|
|
|
403
|
+
# NOTE this is quite costly, a Cipher object is created to encrypt/decrypt each value.
|
|
404
|
+
# another design could keep a persistant cipher.
|
|
405
|
+
class _Cipher:
|
|
406
|
+
"""Not so convincing internal class to abstract cipher algorithms."""
|
|
407
|
+
|
|
408
|
+
def __init__(self, name):
|
|
409
|
+
self._name = name
|
|
410
|
+
if name == "Salsa20":
|
|
411
|
+
from Crypto.Cipher import Salsa20 as cipher
|
|
412
|
+
# NOTE hash and nonce may overlap, which is not an issue
|
|
413
|
+
params = {"key": (32, 64), "nonce": (24, 32)}
|
|
414
|
+
kwargs = {}
|
|
415
|
+
size = 0
|
|
416
|
+
elif name == "ChaCha20":
|
|
417
|
+
from Crypto.Cipher import ChaCha20 as cipher
|
|
418
|
+
# NOTE hash and nonce may overlap, which is not an issue
|
|
419
|
+
params = {"key": (32, 64), "nonce": (24, 32)}
|
|
420
|
+
kwargs = {}
|
|
421
|
+
size = 0
|
|
422
|
+
elif name in ("AES", "AES-128", "AES-128-CBC"):
|
|
423
|
+
from Crypto.Cipher import AES as cipher
|
|
424
|
+
params = {"key": (48, 64), "iv": (32, 48)}
|
|
425
|
+
kwargs = {"mode": cipher.MODE_CBC}
|
|
426
|
+
size = 16
|
|
427
|
+
else:
|
|
428
|
+
raise Exception(f"unexpected cipher: {name}")
|
|
429
|
+
self._cipher = cipher
|
|
430
|
+
self._params = params
|
|
431
|
+
self._kwargs = kwargs
|
|
432
|
+
if size:
|
|
433
|
+
from Crypto.Util.Padding import pad, unpad
|
|
434
|
+
self._pad = lambda s: pad(s, size)
|
|
435
|
+
self._unpad = lambda s: unpad(s, size)
|
|
436
|
+
else:
|
|
437
|
+
self._pad = lambda s: s
|
|
438
|
+
self._unpad = lambda s: s
|
|
439
|
+
|
|
440
|
+
def new(self, derived: bytes):
|
|
441
|
+
kwargs = {k: derived[v[0]: v[1]] for k, v in self._params.items()}
|
|
442
|
+
return self._cipher.new(**kwargs, **self._kwargs) # type: ignore
|
|
443
|
+
|
|
444
|
+
|
|
370
445
|
#
|
|
371
446
|
# Encrypted Cache
|
|
372
447
|
#
|
|
@@ -376,6 +451,7 @@ class EncryptedCache(_KeyMutMapMix, _StatsMix, MutableMapping):
|
|
|
376
451
|
:param secret: bytes of secret, at least 16 bytes.
|
|
377
452
|
:param hsize: size of hashed key, default is *16*.
|
|
378
453
|
:param csize: value checksum size, default is *0*.
|
|
454
|
+
:param cipher: chose cipher from "Salsa20", "AES-128-CBC" or "ChaCha20".
|
|
379
455
|
|
|
380
456
|
The key is *not* encrypted but simply hashed, thus they are
|
|
381
457
|
fixed size with a very low collision probability.
|
|
@@ -384,11 +460,12 @@ class EncryptedCache(_KeyMutMapMix, _StatsMix, MutableMapping):
|
|
|
384
460
|
as each value is encrypted with its own key and nonce.
|
|
385
461
|
|
|
386
462
|
Algorithms:
|
|
463
|
+
|
|
387
464
|
- SHA3: hash/key/nonce derivation and checksum.
|
|
388
|
-
- Salsa20: value encryption.
|
|
465
|
+
- Salsa20, AES-128-CBC or ChaCha20: value encryption.
|
|
389
466
|
"""
|
|
390
467
|
|
|
391
|
-
def __init__(self, cache: MutableMapping, secret: bytes, hsize: int = 16, csize: int = 0):
|
|
468
|
+
def __init__(self, cache: MutableMapping, secret: bytes, hsize: int = 16, csize: int = 0, cipher: str = "Salsa20"):
|
|
392
469
|
self._cache = cache
|
|
393
470
|
assert len(secret) >= 16
|
|
394
471
|
self._secret = secret
|
|
@@ -396,34 +473,31 @@ class EncryptedCache(_KeyMutMapMix, _StatsMix, MutableMapping):
|
|
|
396
473
|
self._hsize = hsize
|
|
397
474
|
assert 0 <= csize <= 32
|
|
398
475
|
self._csize = csize
|
|
399
|
-
|
|
400
|
-
self._cipher = Salsa20
|
|
476
|
+
self._cipher = _Cipher(cipher)
|
|
401
477
|
|
|
402
|
-
def
|
|
403
|
-
"""
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
return (hkey[:self._hsize], hkey[32:], hkey[24:32])
|
|
478
|
+
def _derive(self, key) -> tuple[bytes, bytes]:
|
|
479
|
+
"""Derive hash and stuff from initial key and secret."""
|
|
480
|
+
derived = hashlib.sha3_512(key + self._secret).digest()
|
|
481
|
+
return (derived[:self._hsize], derived)
|
|
407
482
|
|
|
408
483
|
def _key(self, key):
|
|
409
|
-
return self.
|
|
484
|
+
return self._derive(key)[0]
|
|
410
485
|
|
|
411
486
|
def __setitem__(self, key, val):
|
|
412
|
-
hkey,
|
|
413
|
-
xval = self._cipher.new(
|
|
487
|
+
hkey, derived = self._derive(key)
|
|
488
|
+
xval = self._cipher.new(derived).encrypt(self._cipher._pad(val))
|
|
414
489
|
if self._csize:
|
|
415
|
-
|
|
416
|
-
xval = cs + xval
|
|
490
|
+
xval = hashlib.sha3_256(val).digest()[:self._csize] + xval
|
|
417
491
|
self._cache[hkey] = xval
|
|
418
492
|
|
|
419
493
|
def __getitem__(self, key):
|
|
420
|
-
hkey,
|
|
494
|
+
hkey, derived = self._derive(key)
|
|
421
495
|
xval = self._cache[hkey]
|
|
422
496
|
if self._csize: # split cs from encrypted value
|
|
423
497
|
cs, xval = xval[:self._csize], xval[self._csize:]
|
|
424
498
|
else:
|
|
425
499
|
cs = None
|
|
426
|
-
val = self._cipher.new(
|
|
500
|
+
val = self._cipher._unpad(self._cipher.new(derived).decrypt(xval))
|
|
427
501
|
if self._csize and cs != hashlib.sha3_256(val).digest()[:self._csize]:
|
|
428
502
|
raise KeyError(f"invalid encrypted value for key {key}")
|
|
429
503
|
return val
|
|
@@ -597,7 +671,9 @@ class _HashJsonKey:
|
|
|
597
671
|
return self._key
|
|
598
672
|
|
|
599
673
|
|
|
674
|
+
# NOTE not very useful, behaves as _HashJsonKey.
|
|
600
675
|
def hash_json_key(*args, **kwargs):
|
|
676
|
+
"""Function handling of above class, to keep it hidden."""
|
|
601
677
|
return _HashJsonKey(*args, **kwargs)
|
|
602
678
|
|
|
603
679
|
|
|
@@ -639,7 +715,7 @@ class JsonSerde:
|
|
|
639
715
|
elif flag == 3:
|
|
640
716
|
return json.loads(value.decode("utf-8"))
|
|
641
717
|
else:
|
|
642
|
-
raise Exception("Unknown serialization format")
|
|
718
|
+
raise Exception(f"Unknown serialization format: {flag}")
|
|
643
719
|
|
|
644
720
|
|
|
645
721
|
class MemCached(_KeyMutMapMix, MutableMapping):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: CacheToolsUtils
|
|
3
|
-
Version: 10.
|
|
3
|
+
Version: 10.3
|
|
4
4
|
Summary: Cachetools Utilities
|
|
5
5
|
Author-email: Fabien Coelho <cachetools.utils@coelho.net>
|
|
6
6
|
License: CC0
|
|
@@ -53,7 +53,7 @@ Classes to add key prefix and stats to
|
|
|
53
53
|
and other cache-related utils.
|
|
54
54
|
|
|
55
55
|

|
|
56
|
-

|
|
57
57
|

|
|
58
58
|

|
|
59
59
|

|
|
@@ -81,6 +81,7 @@ with `cachetools`.
|
|
|
81
81
|
|
|
82
82
|
- `PrefixedCache`, `PrefixedMemCached` and `PrefixedRedisCache` add a prefix to
|
|
83
83
|
distinguish sources on a shared cache.
|
|
84
|
+
- `AutoPrefixedCache` add a counter-based prefix.
|
|
84
85
|
- `StatsCache`, `MemCached` and `RedisCache` add a `hits` method
|
|
85
86
|
to report the cache hit rate, `stats` to report statistics and
|
|
86
87
|
`reset` to reset statistics.
|
|
@@ -7,7 +7,7 @@ Classes to add key prefix and stats to
|
|
|
7
7
|
and other cache-related utils.
|
|
8
8
|
|
|
9
9
|

|
|
10
|
-

|
|
11
11
|

|
|
12
12
|

|
|
13
13
|

|
|
@@ -35,6 +35,7 @@ with `cachetools`.
|
|
|
35
35
|
|
|
36
36
|
- `PrefixedCache`, `PrefixedMemCached` and `PrefixedRedisCache` add a prefix to
|
|
37
37
|
distinguish sources on a shared cache.
|
|
38
|
+
- `AutoPrefixedCache` add a counter-based prefix.
|
|
38
39
|
- `StatsCache`, `MemCached` and `RedisCache` add a `hits` method
|
|
39
40
|
to report the cache hit rate, `stats` to report statistics and
|
|
40
41
|
`reset` to reset statistics.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|