CacheToolsUtils 10.0__tar.gz → 10.1__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.0 → cachetoolsutils-10.1}/CacheToolsUtils.egg-info/PKG-INFO +2 -1
- {cachetoolsutils-10.0 → cachetoolsutils-10.1}/CacheToolsUtils.py +108 -96
- {cachetoolsutils-10.0 → cachetoolsutils-10.1}/PKG-INFO +2 -1
- {cachetoolsutils-10.0 → cachetoolsutils-10.1}/README.md +1 -0
- {cachetoolsutils-10.0 → cachetoolsutils-10.1}/pyproject.toml +1 -1
- {cachetoolsutils-10.0 → cachetoolsutils-10.1}/CacheToolsUtils.egg-info/SOURCES.txt +0 -0
- {cachetoolsutils-10.0 → cachetoolsutils-10.1}/CacheToolsUtils.egg-info/dependency_links.txt +0 -0
- {cachetoolsutils-10.0 → cachetoolsutils-10.1}/CacheToolsUtils.egg-info/requires.txt +0 -0
- {cachetoolsutils-10.0 → cachetoolsutils-10.1}/CacheToolsUtils.egg-info/top_level.txt +0 -0
- {cachetoolsutils-10.0 → cachetoolsutils-10.1}/LICENSE +0 -0
- {cachetoolsutils-10.0 → cachetoolsutils-10.1}/MANIFEST.in +0 -0
- {cachetoolsutils-10.0 → cachetoolsutils-10.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: CacheToolsUtils
|
|
3
|
-
Version: 10.
|
|
3
|
+
Version: 10.1
|
|
4
4
|
Summary: Cachetools Utilities
|
|
5
5
|
Author-email: Fabien Coelho <cachetools.utils@coelho.net>
|
|
6
6
|
License: CC0
|
|
@@ -89,6 +89,7 @@ with `cachetools`.
|
|
|
89
89
|
- `DebugCache` to trace cache calls using `logging`.
|
|
90
90
|
- `EncryptedCache` a cache with key hashing and value encryption.
|
|
91
91
|
- `ToBytesCache` map keys and values to bytes.
|
|
92
|
+
- `BytesCache` map bytes keys and values to strings.
|
|
92
93
|
|
|
93
94
|
### Cache utilities
|
|
94
95
|
|
|
@@ -367,6 +367,106 @@ class TwoLevelCache(_MutMapMix, MutableMapping):
|
|
|
367
367
|
self._cache2.reset() # type: ignore
|
|
368
368
|
|
|
369
369
|
|
|
370
|
+
#
|
|
371
|
+
# Encrypted Cache
|
|
372
|
+
#
|
|
373
|
+
class EncryptedCache(_KeyMutMapMix, _StatsMix, MutableMapping):
|
|
374
|
+
"""Encrypted Bytes Key-Value Cache.
|
|
375
|
+
|
|
376
|
+
:param secret: bytes of secret, at least 16 bytes.
|
|
377
|
+
:param hsize: size of hashed key, default is *16*.
|
|
378
|
+
:param csize: value checksum size, default is *0*.
|
|
379
|
+
|
|
380
|
+
The key is *not* encrypted but simply hashed, thus they are
|
|
381
|
+
fixed size with a very low collision probability.
|
|
382
|
+
|
|
383
|
+
By design, the clear-text key is needed to recover the value,
|
|
384
|
+
as each value is encrypted with its own key and nonce.
|
|
385
|
+
|
|
386
|
+
Algorithms:
|
|
387
|
+
- SHA3: hash/key/nonce derivation and checksum.
|
|
388
|
+
- Salsa20: value encryption.
|
|
389
|
+
"""
|
|
390
|
+
|
|
391
|
+
def __init__(self, cache: MutableMapping, secret: bytes, hsize: int = 16, csize: int = 0):
|
|
392
|
+
self._cache = cache
|
|
393
|
+
assert len(secret) >= 16
|
|
394
|
+
self._secret = secret
|
|
395
|
+
assert 8 <= hsize <= 32
|
|
396
|
+
self._hsize = hsize
|
|
397
|
+
assert 0 <= csize <= 32
|
|
398
|
+
self._csize = csize
|
|
399
|
+
from Crypto.Cipher import Salsa20
|
|
400
|
+
self._cipher = Salsa20
|
|
401
|
+
|
|
402
|
+
def _keydev(self, key) -> tuple[bytes, bytes, bytes]:
|
|
403
|
+
"""Compute hash, key and nonce from initial key."""
|
|
404
|
+
hkey = hashlib.sha3_512(key + self._secret).digest()
|
|
405
|
+
# NOTE hash and nonce may overlap, which is not an issue
|
|
406
|
+
return (hkey[:self._hsize], hkey[32:], hkey[24:32])
|
|
407
|
+
|
|
408
|
+
def _key(self, key):
|
|
409
|
+
return self._keydev(key)[0]
|
|
410
|
+
|
|
411
|
+
def __setitem__(self, key, val):
|
|
412
|
+
hkey, vkey, vnonce = self._keydev(key)
|
|
413
|
+
xval = self._cipher.new(key=vkey, nonce=vnonce).encrypt(val)
|
|
414
|
+
if self._csize:
|
|
415
|
+
cs = hashlib.sha3_256(val).digest()[:self._csize]
|
|
416
|
+
xval = cs + xval
|
|
417
|
+
self._cache[hkey] = xval
|
|
418
|
+
|
|
419
|
+
def __getitem__(self, key):
|
|
420
|
+
hkey, vkey, vnonce = self._keydev(key)
|
|
421
|
+
xval = self._cache[hkey]
|
|
422
|
+
if self._csize: # split cs from encrypted value
|
|
423
|
+
cs, xval = xval[:self._csize], xval[self._csize:]
|
|
424
|
+
else:
|
|
425
|
+
cs = None
|
|
426
|
+
val = self._cipher.new(key=vkey, nonce=vnonce).decrypt(xval)
|
|
427
|
+
if self._csize and cs != hashlib.sha3_256(val).digest()[:self._csize]:
|
|
428
|
+
raise KeyError(f"invalid encrypted value for key {key}")
|
|
429
|
+
return val
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
class BytesCache(_KeyMutMapMix, _StatsMix, MutableMapping):
|
|
433
|
+
"""Map bytes to strings."""
|
|
434
|
+
|
|
435
|
+
def __init__(self, cache):
|
|
436
|
+
self._cache = cache
|
|
437
|
+
|
|
438
|
+
def _key(self, key):
|
|
439
|
+
# assert isinstance(key, bytes)
|
|
440
|
+
return base64.b85encode(key).decode("ASCII")
|
|
441
|
+
|
|
442
|
+
def __setitem__(self, key, val):
|
|
443
|
+
self._cache.__setitem__(self._key(key), self._key(val))
|
|
444
|
+
|
|
445
|
+
def __getitem__(self, key):
|
|
446
|
+
val = self._cache.__getitem__(self._key(key))
|
|
447
|
+
# assert isinstance(val, str)
|
|
448
|
+
return base64.b85decode(val)
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
class ToBytesCache(_KeyMutMapMix, _StatsMix, MutableMapping):
|
|
452
|
+
"""Map (JSON-serializable) cache keys and values to bytes."""
|
|
453
|
+
|
|
454
|
+
def __init__(self, cache):
|
|
455
|
+
self._cache = cache
|
|
456
|
+
|
|
457
|
+
def _key(self, key):
|
|
458
|
+
return json.dumps(key, sort_keys=True, separators=(",", ":")).encode("UTF-8")
|
|
459
|
+
|
|
460
|
+
def __setitem__(self, key, val):
|
|
461
|
+
self._cache.__setitem__(self._key(key), self._key(val))
|
|
462
|
+
|
|
463
|
+
def __getitem__(self, key):
|
|
464
|
+
return json.loads(self._cache.__getitem__(self._key(key)).decode("UTF-8"))
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
#
|
|
468
|
+
# CACHED DECORATOR AND HELPERS
|
|
469
|
+
#
|
|
370
470
|
def cached(cache, *args, **kwargs):
|
|
371
471
|
"""Extended decorator with delete and exists.
|
|
372
472
|
|
|
@@ -508,93 +608,9 @@ def full_hash_key(*args, **kwargs) -> str:
|
|
|
508
608
|
return base64.b85encode(hkey).decode("ASCII")
|
|
509
609
|
|
|
510
610
|
|
|
511
|
-
#
|
|
512
|
-
# Encrypted Cache
|
|
513
|
-
#
|
|
514
|
-
class EncryptedCache(_KeyMutMapMix, _StatsMix, MutableMapping):
|
|
515
|
-
"""Encrypted Bytes Key-Value Cache.
|
|
516
|
-
|
|
517
|
-
:param secret: bytes of secret, at least 16 bytes.
|
|
518
|
-
:param hsize: size of hashed key, default is 16.
|
|
519
|
-
|
|
520
|
-
The key is *not* encrypted but simply hashed, thus they are
|
|
521
|
-
fixed size with a very low collision probability.
|
|
522
|
-
|
|
523
|
-
By design, the clear-text key is needed to recover the value,
|
|
524
|
-
as each value is encrypted with its own key.
|
|
525
|
-
|
|
526
|
-
There is no integrity check on the value.
|
|
527
|
-
|
|
528
|
-
Algorithms:
|
|
529
|
-
- SHA3: hash/key/nonce derivation.
|
|
530
|
-
- Salsa20: value encryption.
|
|
531
|
-
"""
|
|
532
|
-
|
|
533
|
-
def __init__(self, cache: MutableMapping, secret: bytes, hsize: int = 16):
|
|
534
|
-
self._cache = cache
|
|
535
|
-
assert len(secret) >= 16
|
|
536
|
-
self._secret = secret
|
|
537
|
-
assert 8 <= hsize <= 24
|
|
538
|
-
self._hsize = hsize
|
|
539
|
-
from Crypto.Cipher import Salsa20
|
|
540
|
-
self._cipher = Salsa20
|
|
541
|
-
|
|
542
|
-
def _keydev(self, key):
|
|
543
|
-
hkey = hashlib.sha3_512(key + self._secret).digest()
|
|
544
|
-
sz = self._hsize
|
|
545
|
-
return (hkey[:sz], hkey[sz:sz+32], hkey[sz+32:sz+40])
|
|
546
|
-
|
|
547
|
-
def _key(self, key):
|
|
548
|
-
return self._keydev(key)[0]
|
|
549
|
-
|
|
550
|
-
def __setitem__(self, key, val):
|
|
551
|
-
hkey, vkey, vnonce = self._keydev(key)
|
|
552
|
-
self._cache[hkey] = self._cipher.new(key=vkey, nonce=vnonce).encrypt(val)
|
|
553
|
-
|
|
554
|
-
def __getitem__(self, key):
|
|
555
|
-
hkey, vkey, vnonce = self._keydev(key)
|
|
556
|
-
return self._cipher.new(key=vkey, nonce=vnonce).decrypt(self._cache[hkey])
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
class BytesCache(_KeyMutMapMix, _StatsMix, MutableMapping):
|
|
560
|
-
"""Map bytes to strings."""
|
|
561
|
-
|
|
562
|
-
def __init__(self, cache):
|
|
563
|
-
self._cache = cache
|
|
564
|
-
|
|
565
|
-
def _key(self, key):
|
|
566
|
-
# assert isinstance(key, bytes)
|
|
567
|
-
return base64.b85encode(key).decode("ASCII")
|
|
568
|
-
|
|
569
|
-
def __setitem__(self, key, val):
|
|
570
|
-
self._cache.__setitem__(self._key(key), self._key(val))
|
|
571
|
-
|
|
572
|
-
def __getitem__(self, key):
|
|
573
|
-
val = self._cache.__getitem__(self._key(key))
|
|
574
|
-
# assert isinstance(val, str)
|
|
575
|
-
return base64.b85decode(val)
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
class ToBytesCache(_KeyMutMapMix, _StatsMix, MutableMapping):
|
|
579
|
-
"""Map (JSON-serializable) cache keys and values to bytes."""
|
|
580
|
-
|
|
581
|
-
def __init__(self, cache):
|
|
582
|
-
self._cache = cache
|
|
583
|
-
|
|
584
|
-
def _key(self, key):
|
|
585
|
-
return json.dumps(key, sort_keys=True, separators=(",", ":")).encode("UTF-8")
|
|
586
|
-
|
|
587
|
-
def __setitem__(self, key, val):
|
|
588
|
-
self._cache.__setitem__(self._key(key), self._key(val))
|
|
589
|
-
|
|
590
|
-
def __getitem__(self, key):
|
|
591
|
-
return json.loads(self._cache.__getitem__(self._key(key)).decode("UTF-8"))
|
|
592
|
-
|
|
593
|
-
|
|
594
611
|
#
|
|
595
612
|
# MEMCACHED
|
|
596
613
|
#
|
|
597
|
-
# FIXME what about bytes?
|
|
598
614
|
class JsonSerde:
|
|
599
615
|
"""JSON serialize/deserialize class for MemCached (``pymemcache``).
|
|
600
616
|
|
|
@@ -607,21 +623,21 @@ class JsonSerde:
|
|
|
607
623
|
|
|
608
624
|
# keep strings, else json
|
|
609
625
|
def serialize(self, key, value):
|
|
610
|
-
if isinstance(value,
|
|
611
|
-
return value
|
|
612
|
-
elif isinstance(value,
|
|
613
|
-
return value,
|
|
626
|
+
if isinstance(value, bytes):
|
|
627
|
+
return value, 1
|
|
628
|
+
elif isinstance(value, str):
|
|
629
|
+
return value.encode("utf-8"), 2
|
|
614
630
|
else:
|
|
615
|
-
return json.dumps(value, sort_keys=True, separators=(",", ":")).encode("utf-8"),
|
|
631
|
+
return json.dumps(value, sort_keys=True, separators=(",", ":")).encode("utf-8"), 3
|
|
616
632
|
|
|
617
633
|
# reverse previous serialization
|
|
618
634
|
def deserialize(self, key, value, flag):
|
|
619
635
|
if flag == 1:
|
|
620
|
-
return value
|
|
636
|
+
return value
|
|
621
637
|
elif flag == 2:
|
|
622
|
-
return
|
|
638
|
+
return value.decode("utf-8")
|
|
623
639
|
elif flag == 3:
|
|
624
|
-
return value
|
|
640
|
+
return json.loads(value.decode("utf-8"))
|
|
625
641
|
else:
|
|
626
642
|
raise Exception("Unknown serialization format")
|
|
627
643
|
|
|
@@ -650,8 +666,6 @@ class MemCached(_KeyMutMapMix, MutableMapping):
|
|
|
650
666
|
# short (250 bytes) ASCII without control chars nor spaces
|
|
651
667
|
# we do not use hashing which might be costly or induce collisions
|
|
652
668
|
def _key(self, key):
|
|
653
|
-
import base64
|
|
654
|
-
|
|
655
669
|
return base64.b85encode(str(key).encode("utf-8"))
|
|
656
670
|
|
|
657
671
|
def __len__(self):
|
|
@@ -690,8 +704,6 @@ class PrefixedMemCached(MemCached):
|
|
|
690
704
|
self._prefix = bytes(prefix, "utf-8")
|
|
691
705
|
|
|
692
706
|
def _key(self, key):
|
|
693
|
-
import base64
|
|
694
|
-
|
|
695
707
|
return self._prefix + base64.b85encode(str(key).encode("utf-8"))
|
|
696
708
|
|
|
697
709
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: CacheToolsUtils
|
|
3
|
-
Version: 10.
|
|
3
|
+
Version: 10.1
|
|
4
4
|
Summary: Cachetools Utilities
|
|
5
5
|
Author-email: Fabien Coelho <cachetools.utils@coelho.net>
|
|
6
6
|
License: CC0
|
|
@@ -89,6 +89,7 @@ with `cachetools`.
|
|
|
89
89
|
- `DebugCache` to trace cache calls using `logging`.
|
|
90
90
|
- `EncryptedCache` a cache with key hashing and value encryption.
|
|
91
91
|
- `ToBytesCache` map keys and values to bytes.
|
|
92
|
+
- `BytesCache` map bytes keys and values to strings.
|
|
92
93
|
|
|
93
94
|
### Cache utilities
|
|
94
95
|
|
|
@@ -43,6 +43,7 @@ with `cachetools`.
|
|
|
43
43
|
- `DebugCache` to trace cache calls using `logging`.
|
|
44
44
|
- `EncryptedCache` a cache with key hashing and value encryption.
|
|
45
45
|
- `ToBytesCache` map keys and values to bytes.
|
|
46
|
+
- `BytesCache` map bytes keys and values to strings.
|
|
46
47
|
|
|
47
48
|
### Cache utilities
|
|
48
49
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|