cachetools 5.5.2__tar.gz → 6.0.0b2__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.
- {cachetools-5.5.2 → cachetools-6.0.0b2}/CHANGELOG.rst +24 -0
- {cachetools-5.5.2/src/cachetools.egg-info → cachetools-6.0.0b2}/PKG-INFO +3 -4
- {cachetools-5.5.2 → cachetools-6.0.0b2}/docs/index.rst +1 -25
- {cachetools-5.5.2 → cachetools-6.0.0b2}/setup.cfg +0 -2
- {cachetools-5.5.2 → cachetools-6.0.0b2}/src/cachetools/__init__.py +18 -97
- cachetools-5.5.2/src/cachetools/_decorators.py → cachetools-6.0.0b2/src/cachetools/_cached.py +93 -8
- cachetools-6.0.0b2/src/cachetools/_cachedmethod.py +108 -0
- {cachetools-5.5.2 → cachetools-6.0.0b2}/src/cachetools/func.py +6 -22
- {cachetools-5.5.2 → cachetools-6.0.0b2/src/cachetools.egg-info}/PKG-INFO +3 -4
- {cachetools-5.5.2 → cachetools-6.0.0b2}/src/cachetools.egg-info/SOURCES.txt +2 -2
- {cachetools-5.5.2 → cachetools-6.0.0b2}/tests/test_cached.py +151 -0
- {cachetools-5.5.2 → cachetools-6.0.0b2}/tests/test_cachedmethod.py +138 -20
- {cachetools-5.5.2 → cachetools-6.0.0b2}/tests/test_func.py +0 -12
- cachetools-5.5.2/tests/test_mru.py +0 -63
- {cachetools-5.5.2 → cachetools-6.0.0b2}/LICENSE +0 -0
- {cachetools-5.5.2 → cachetools-6.0.0b2}/MANIFEST.in +0 -0
- {cachetools-5.5.2 → cachetools-6.0.0b2}/README.rst +0 -0
- {cachetools-5.5.2 → cachetools-6.0.0b2}/docs/conf.py +0 -0
- {cachetools-5.5.2 → cachetools-6.0.0b2}/pyproject.toml +0 -0
- {cachetools-5.5.2 → cachetools-6.0.0b2}/setup.py +0 -0
- {cachetools-5.5.2 → cachetools-6.0.0b2}/src/cachetools/keys.py +0 -0
- {cachetools-5.5.2 → cachetools-6.0.0b2}/src/cachetools.egg-info/dependency_links.txt +0 -0
- {cachetools-5.5.2 → cachetools-6.0.0b2}/src/cachetools.egg-info/top_level.txt +0 -0
- {cachetools-5.5.2 → cachetools-6.0.0b2}/tests/__init__.py +0 -0
- {cachetools-5.5.2 → cachetools-6.0.0b2}/tests/test_cache.py +0 -0
- {cachetools-5.5.2 → cachetools-6.0.0b2}/tests/test_fifo.py +0 -0
- {cachetools-5.5.2 → cachetools-6.0.0b2}/tests/test_keys.py +0 -0
- {cachetools-5.5.2 → cachetools-6.0.0b2}/tests/test_lfu.py +0 -0
- {cachetools-5.5.2 → cachetools-6.0.0b2}/tests/test_lru.py +0 -0
- {cachetools-5.5.2 → cachetools-6.0.0b2}/tests/test_rr.py +0 -0
- {cachetools-5.5.2 → cachetools-6.0.0b2}/tests/test_tlru.py +0 -0
- {cachetools-5.5.2 → cachetools-6.0.0b2}/tests/test_ttl.py +0 -0
- {cachetools-5.5.2 → cachetools-6.0.0b2}/tox.ini +0 -0
|
@@ -1,3 +1,27 @@
|
|
|
1
|
+
v6.0.0 (UNRELEASED)
|
|
2
|
+
===================
|
|
3
|
+
|
|
4
|
+
- Require Python 3.9 or later (breaking change).
|
|
5
|
+
|
|
6
|
+
- Remove ``MRUCache`` and the ``@func.mru_cache`` decorator (breaking
|
|
7
|
+
change).
|
|
8
|
+
|
|
9
|
+
- Add an optional ``condition`` parameter to the ``@cached`` and
|
|
10
|
+
``@cachedmethod`` decorators, which, when used with a
|
|
11
|
+
``threading.Condition`` instance, should improve `cache stampede
|
|
12
|
+
<https://en.wikipedia.org/wiki/Cache_stampede>`_ issues in massively
|
|
13
|
+
parallel environments. Note that this will incur some performance
|
|
14
|
+
penalty, and therefore has to be enabled explicitly.
|
|
15
|
+
|
|
16
|
+
- Convert the ``cachetools.func`` decorators to use a
|
|
17
|
+
``threading.Condition`` instance to deal with `cache stampede
|
|
18
|
+
<https://en.wikipedia.org/wiki/Cache_stampede>`_ issues. Note that
|
|
19
|
+
this *may* result in a noticable performance degradation, depending
|
|
20
|
+
on your actual use case.
|
|
21
|
+
|
|
22
|
+
- Update CI environment.
|
|
23
|
+
|
|
24
|
+
|
|
1
25
|
v5.5.2 (2025-02-20)
|
|
2
26
|
===================
|
|
3
27
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: cachetools
|
|
3
|
-
Version:
|
|
3
|
+
Version: 6.0.0b2
|
|
4
4
|
Summary: Extensible memoizing collections and decorators
|
|
5
5
|
Home-page: https://github.com/tkem/cachetools/
|
|
6
6
|
Author: Thomas Kemmer
|
|
@@ -13,8 +13,6 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
13
13
|
Classifier: Operating System :: OS Independent
|
|
14
14
|
Classifier: Programming Language :: Python
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
18
16
|
Classifier: Programming Language :: Python :: 3.9
|
|
19
17
|
Classifier: Programming Language :: Python :: 3.10
|
|
20
18
|
Classifier: Programming Language :: Python :: 3.11
|
|
@@ -23,6 +21,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
23
21
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
22
|
Requires-Python: >=3.7
|
|
25
23
|
License-File: LICENSE
|
|
24
|
+
Dynamic: license-file
|
|
26
25
|
|
|
27
26
|
cachetools
|
|
28
27
|
========================================================================
|
|
@@ -98,18 +98,6 @@ inserted into the cache.
|
|
|
98
98
|
This class discards the least recently used items first to make
|
|
99
99
|
space when necessary.
|
|
100
100
|
|
|
101
|
-
.. autoclass:: MRUCache(maxsize, getsizeof=None)
|
|
102
|
-
:members: popitem
|
|
103
|
-
|
|
104
|
-
This class discards the most recently used items first to make
|
|
105
|
-
space when necessary.
|
|
106
|
-
|
|
107
|
-
.. deprecated:: 5.4
|
|
108
|
-
|
|
109
|
-
`MRUCache` has been deprecated due to lack of use, to reduce
|
|
110
|
-
maintenance. Please choose another cache implementation that suits
|
|
111
|
-
your needs.
|
|
112
|
-
|
|
113
101
|
.. autoclass:: RRCache(maxsize, choice=random.choice, getsizeof=None)
|
|
114
102
|
:members: choice, popitem
|
|
115
103
|
|
|
@@ -174,7 +162,7 @@ inserted into the cache.
|
|
|
174
162
|
value of `timer()`.
|
|
175
163
|
|
|
176
164
|
.. testcode::
|
|
177
|
-
|
|
165
|
+
|
|
178
166
|
def my_ttu(_key, value, now):
|
|
179
167
|
# assume value.ttu contains the item's time-to-use in seconds
|
|
180
168
|
# note that the _key argument is ignored in this example
|
|
@@ -688,18 +676,6 @@ all the decorators in this module are thread-safe by default.
|
|
|
688
676
|
saves up to `maxsize` results based on a Least Recently Used (LRU)
|
|
689
677
|
algorithm.
|
|
690
678
|
|
|
691
|
-
.. decorator:: mru_cache(user_function)
|
|
692
|
-
mru_cache(maxsize=128, typed=False)
|
|
693
|
-
|
|
694
|
-
Decorator that wraps a function with a memoizing callable that
|
|
695
|
-
saves up to `maxsize` results based on a Most Recently Used (MRU)
|
|
696
|
-
algorithm.
|
|
697
|
-
|
|
698
|
-
.. deprecated:: 5.4
|
|
699
|
-
|
|
700
|
-
The `mru_cache` decorator has been deprecated due to lack of use.
|
|
701
|
-
Please choose a decorator based on some other algorithm.
|
|
702
|
-
|
|
703
679
|
.. decorator:: rr_cache(user_function)
|
|
704
680
|
rr_cache(maxsize=128, choice=random.choice, typed=False)
|
|
705
681
|
|
|
@@ -16,8 +16,6 @@ classifiers =
|
|
|
16
16
|
Operating System :: OS Independent
|
|
17
17
|
Programming Language :: Python
|
|
18
18
|
Programming Language :: Python :: 3
|
|
19
|
-
Programming Language :: Python :: 3.7
|
|
20
|
-
Programming Language :: Python :: 3.8
|
|
21
19
|
Programming Language :: Python :: 3.9
|
|
22
20
|
Programming Language :: Python :: 3.10
|
|
23
21
|
Programming Language :: Python :: 3.11
|
|
@@ -5,7 +5,6 @@ __all__ = (
|
|
|
5
5
|
"FIFOCache",
|
|
6
6
|
"LFUCache",
|
|
7
7
|
"LRUCache",
|
|
8
|
-
"MRUCache",
|
|
9
8
|
"RRCache",
|
|
10
9
|
"TLRUCache",
|
|
11
10
|
"TTLCache",
|
|
@@ -13,7 +12,7 @@ __all__ = (
|
|
|
13
12
|
"cachedmethod",
|
|
14
13
|
)
|
|
15
14
|
|
|
16
|
-
__version__ = "
|
|
15
|
+
__version__ = "6.0.0b2"
|
|
17
16
|
|
|
18
17
|
import collections
|
|
19
18
|
import collections.abc
|
|
@@ -23,7 +22,8 @@ import random
|
|
|
23
22
|
import time
|
|
24
23
|
|
|
25
24
|
from . import keys
|
|
26
|
-
from .
|
|
25
|
+
from ._cached import _cached_wrapper
|
|
26
|
+
from ._cachedmethod import _cachedmethod_wrapper
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
class _DefaultSize:
|
|
@@ -237,47 +237,6 @@ class LRUCache(Cache):
|
|
|
237
237
|
self.__order[key] = None
|
|
238
238
|
|
|
239
239
|
|
|
240
|
-
class MRUCache(Cache):
|
|
241
|
-
"""Most Recently Used (MRU) cache implementation."""
|
|
242
|
-
|
|
243
|
-
def __init__(self, maxsize, getsizeof=None):
|
|
244
|
-
from warnings import warn
|
|
245
|
-
|
|
246
|
-
warn("MRUCache is deprecated", DeprecationWarning, stacklevel=2)
|
|
247
|
-
|
|
248
|
-
Cache.__init__(self, maxsize, getsizeof)
|
|
249
|
-
self.__order = collections.OrderedDict()
|
|
250
|
-
|
|
251
|
-
def __getitem__(self, key, cache_getitem=Cache.__getitem__):
|
|
252
|
-
value = cache_getitem(self, key)
|
|
253
|
-
if key in self: # __missing__ may not store item
|
|
254
|
-
self.__update(key)
|
|
255
|
-
return value
|
|
256
|
-
|
|
257
|
-
def __setitem__(self, key, value, cache_setitem=Cache.__setitem__):
|
|
258
|
-
cache_setitem(self, key, value)
|
|
259
|
-
self.__update(key)
|
|
260
|
-
|
|
261
|
-
def __delitem__(self, key, cache_delitem=Cache.__delitem__):
|
|
262
|
-
cache_delitem(self, key)
|
|
263
|
-
del self.__order[key]
|
|
264
|
-
|
|
265
|
-
def popitem(self):
|
|
266
|
-
"""Remove and return the `(key, value)` pair most recently used."""
|
|
267
|
-
try:
|
|
268
|
-
key = next(iter(self.__order))
|
|
269
|
-
except StopIteration:
|
|
270
|
-
raise KeyError("%s is empty" % type(self).__name__) from None
|
|
271
|
-
else:
|
|
272
|
-
return (key, self.pop(key))
|
|
273
|
-
|
|
274
|
-
def __update(self, key):
|
|
275
|
-
try:
|
|
276
|
-
self.__order.move_to_end(key, last=False)
|
|
277
|
-
except KeyError:
|
|
278
|
-
self.__order[key] = None
|
|
279
|
-
|
|
280
|
-
|
|
281
240
|
class RRCache(Cache):
|
|
282
241
|
"""Random Replacement (RR) cache implementation."""
|
|
283
242
|
|
|
@@ -636,11 +595,21 @@ _CacheInfo = collections.namedtuple(
|
|
|
636
595
|
)
|
|
637
596
|
|
|
638
597
|
|
|
639
|
-
def cached(cache, key=keys.hashkey, lock=None, info=False):
|
|
598
|
+
def cached(cache, key=keys.hashkey, lock=None, condition=None, info=False):
|
|
640
599
|
"""Decorator to wrap a function with a memoizing callable that saves
|
|
641
600
|
results in a cache.
|
|
642
601
|
|
|
643
602
|
"""
|
|
603
|
+
if isinstance(condition, bool):
|
|
604
|
+
from warnings import warn
|
|
605
|
+
|
|
606
|
+
warn(
|
|
607
|
+
"passing `info` as positional parameter is deprecated",
|
|
608
|
+
DeprecationWarning,
|
|
609
|
+
stacklevel=2,
|
|
610
|
+
)
|
|
611
|
+
info = condition
|
|
612
|
+
condition = None
|
|
644
613
|
|
|
645
614
|
def decorator(func):
|
|
646
615
|
if info:
|
|
@@ -659,9 +628,9 @@ def cached(cache, key=keys.hashkey, lock=None, info=False):
|
|
|
659
628
|
def make_info(hits, misses):
|
|
660
629
|
return _CacheInfo(hits, misses, 0, 0)
|
|
661
630
|
|
|
662
|
-
wrapper = _cached_wrapper(func, cache, key, lock, make_info)
|
|
631
|
+
wrapper = _cached_wrapper(func, cache, key, lock, condition, info=make_info)
|
|
663
632
|
else:
|
|
664
|
-
wrapper = _cached_wrapper(func, cache, key, lock,
|
|
633
|
+
wrapper = _cached_wrapper(func, cache, key, lock, condition)
|
|
665
634
|
|
|
666
635
|
wrapper.cache = cache
|
|
667
636
|
wrapper.cache_key = key
|
|
@@ -672,66 +641,18 @@ def cached(cache, key=keys.hashkey, lock=None, info=False):
|
|
|
672
641
|
return decorator
|
|
673
642
|
|
|
674
643
|
|
|
675
|
-
def cachedmethod(cache, key=keys.methodkey, lock=None):
|
|
644
|
+
def cachedmethod(cache, key=keys.methodkey, lock=None, condition=None):
|
|
676
645
|
"""Decorator to wrap a class or instance method with a memoizing
|
|
677
646
|
callable that saves results in a cache.
|
|
678
647
|
|
|
679
648
|
"""
|
|
680
649
|
|
|
681
650
|
def decorator(method):
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
def wrapper(self, *args, **kwargs):
|
|
685
|
-
c = cache(self)
|
|
686
|
-
if c is None:
|
|
687
|
-
return method(self, *args, **kwargs)
|
|
688
|
-
k = key(self, *args, **kwargs)
|
|
689
|
-
try:
|
|
690
|
-
return c[k]
|
|
691
|
-
except KeyError:
|
|
692
|
-
pass # key not found
|
|
693
|
-
v = method(self, *args, **kwargs)
|
|
694
|
-
try:
|
|
695
|
-
c[k] = v
|
|
696
|
-
except ValueError:
|
|
697
|
-
pass # value too large
|
|
698
|
-
return v
|
|
699
|
-
|
|
700
|
-
def clear(self):
|
|
701
|
-
c = cache(self)
|
|
702
|
-
if c is not None:
|
|
703
|
-
c.clear()
|
|
704
|
-
|
|
705
|
-
else:
|
|
706
|
-
|
|
707
|
-
def wrapper(self, *args, **kwargs):
|
|
708
|
-
c = cache(self)
|
|
709
|
-
if c is None:
|
|
710
|
-
return method(self, *args, **kwargs)
|
|
711
|
-
k = key(self, *args, **kwargs)
|
|
712
|
-
try:
|
|
713
|
-
with lock(self):
|
|
714
|
-
return c[k]
|
|
715
|
-
except KeyError:
|
|
716
|
-
pass # key not found
|
|
717
|
-
v = method(self, *args, **kwargs)
|
|
718
|
-
# in case of a race, prefer the item already in the cache
|
|
719
|
-
try:
|
|
720
|
-
with lock(self):
|
|
721
|
-
return c.setdefault(k, v)
|
|
722
|
-
except ValueError:
|
|
723
|
-
return v # value too large
|
|
724
|
-
|
|
725
|
-
def clear(self):
|
|
726
|
-
c = cache(self)
|
|
727
|
-
if c is not None:
|
|
728
|
-
with lock(self):
|
|
729
|
-
c.clear()
|
|
651
|
+
wrapper = _cachedmethod_wrapper(method, cache, key, lock, condition)
|
|
730
652
|
|
|
731
653
|
wrapper.cache = cache
|
|
732
654
|
wrapper.cache_key = key
|
|
733
655
|
wrapper.cache_lock = lock
|
|
734
|
-
wrapper.cache_clear = clear
|
|
735
656
|
|
|
736
657
|
return functools.update_wrapper(wrapper, method)
|
|
737
658
|
|
cachetools-5.5.2/src/cachetools/_decorators.py → cachetools-6.0.0b2/src/cachetools/_cached.py
RENAMED
|
@@ -1,4 +1,48 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Function decorator helpers."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def _cached_condition_info(func, cache, key, lock, cond, info):
|
|
5
|
+
hits = misses = 0
|
|
6
|
+
pending = set()
|
|
7
|
+
|
|
8
|
+
def wrapper(*args, **kwargs):
|
|
9
|
+
nonlocal hits, misses
|
|
10
|
+
k = key(*args, **kwargs)
|
|
11
|
+
with lock:
|
|
12
|
+
cond.wait_for(lambda: k not in pending)
|
|
13
|
+
try:
|
|
14
|
+
result = cache[k]
|
|
15
|
+
hits += 1
|
|
16
|
+
return result
|
|
17
|
+
except KeyError:
|
|
18
|
+
pending.add(k)
|
|
19
|
+
misses += 1
|
|
20
|
+
try:
|
|
21
|
+
v = func(*args, **kwargs)
|
|
22
|
+
with lock:
|
|
23
|
+
try:
|
|
24
|
+
cache[k] = v
|
|
25
|
+
except ValueError:
|
|
26
|
+
pass # value too large
|
|
27
|
+
return v
|
|
28
|
+
finally:
|
|
29
|
+
with lock:
|
|
30
|
+
pending.remove(k)
|
|
31
|
+
cond.notify_all()
|
|
32
|
+
|
|
33
|
+
def cache_clear():
|
|
34
|
+
nonlocal hits, misses
|
|
35
|
+
with lock:
|
|
36
|
+
cache.clear()
|
|
37
|
+
hits = misses = 0
|
|
38
|
+
|
|
39
|
+
def cache_info():
|
|
40
|
+
with lock:
|
|
41
|
+
return info(hits, misses)
|
|
42
|
+
|
|
43
|
+
wrapper.cache_clear = cache_clear
|
|
44
|
+
wrapper.cache_info = cache_info
|
|
45
|
+
return wrapper
|
|
2
46
|
|
|
3
47
|
|
|
4
48
|
def _cached_locked_info(func, cache, key, lock, info):
|
|
@@ -83,6 +127,39 @@ def _uncached_info(func, info):
|
|
|
83
127
|
return wrapper
|
|
84
128
|
|
|
85
129
|
|
|
130
|
+
def _cached_condition(func, cache, key, lock, cond):
|
|
131
|
+
pending = set()
|
|
132
|
+
|
|
133
|
+
def wrapper(*args, **kwargs):
|
|
134
|
+
k = key(*args, **kwargs)
|
|
135
|
+
with lock:
|
|
136
|
+
cond.wait_for(lambda: k not in pending)
|
|
137
|
+
try:
|
|
138
|
+
result = cache[k]
|
|
139
|
+
return result
|
|
140
|
+
except KeyError:
|
|
141
|
+
pending.add(k)
|
|
142
|
+
try:
|
|
143
|
+
v = func(*args, **kwargs)
|
|
144
|
+
with lock:
|
|
145
|
+
try:
|
|
146
|
+
cache[k] = v
|
|
147
|
+
except ValueError:
|
|
148
|
+
pass # value too large
|
|
149
|
+
return v
|
|
150
|
+
finally:
|
|
151
|
+
with lock:
|
|
152
|
+
pending.remove(k)
|
|
153
|
+
cond.notify_all()
|
|
154
|
+
|
|
155
|
+
def cache_clear():
|
|
156
|
+
with lock:
|
|
157
|
+
cache.clear()
|
|
158
|
+
|
|
159
|
+
wrapper.cache_clear = cache_clear
|
|
160
|
+
return wrapper
|
|
161
|
+
|
|
162
|
+
|
|
86
163
|
def _cached_locked(func, cache, key, lock):
|
|
87
164
|
def wrapper(*args, **kwargs):
|
|
88
165
|
k = key(*args, **kwargs)
|
|
@@ -133,20 +210,28 @@ def _uncached(func):
|
|
|
133
210
|
return wrapper
|
|
134
211
|
|
|
135
212
|
|
|
136
|
-
def _cached_wrapper(func, cache, key, lock, info):
|
|
213
|
+
def _cached_wrapper(func, cache, key, lock=None, cond=None, info=None):
|
|
137
214
|
if info is not None:
|
|
138
215
|
if cache is None:
|
|
139
216
|
wrapper = _uncached_info(func, info)
|
|
140
|
-
elif lock is None:
|
|
141
|
-
wrapper =
|
|
142
|
-
|
|
217
|
+
elif cond is not None and lock is not None:
|
|
218
|
+
wrapper = _cached_condition_info(func, cache, key, lock, cond, info)
|
|
219
|
+
elif cond is not None:
|
|
220
|
+
wrapper = _cached_condition_info(func, cache, key, cond, cond, info)
|
|
221
|
+
elif lock is not None:
|
|
143
222
|
wrapper = _cached_locked_info(func, cache, key, lock, info)
|
|
223
|
+
else:
|
|
224
|
+
wrapper = _cached_unlocked_info(func, cache, key, info)
|
|
144
225
|
else:
|
|
145
226
|
if cache is None:
|
|
146
227
|
wrapper = _uncached(func)
|
|
147
|
-
elif lock is None:
|
|
148
|
-
wrapper =
|
|
149
|
-
|
|
228
|
+
elif cond is not None and lock is not None:
|
|
229
|
+
wrapper = _cached_condition(func, cache, key, lock, cond)
|
|
230
|
+
elif cond is not None:
|
|
231
|
+
wrapper = _cached_condition(func, cache, key, cond, cond)
|
|
232
|
+
elif lock is not None:
|
|
150
233
|
wrapper = _cached_locked(func, cache, key, lock)
|
|
234
|
+
else:
|
|
235
|
+
wrapper = _cached_unlocked(func, cache, key)
|
|
151
236
|
wrapper.cache_info = None
|
|
152
237
|
return wrapper
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Method decorator helpers."""
|
|
2
|
+
|
|
3
|
+
import weakref
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _cachedmethod_condition(method, cache, key, lock, cond):
|
|
7
|
+
pending = weakref.WeakKeyDictionary()
|
|
8
|
+
|
|
9
|
+
def wrapper(self, *args, **kwargs):
|
|
10
|
+
c = cache(self)
|
|
11
|
+
if c is None:
|
|
12
|
+
return method(self, *args, **kwargs)
|
|
13
|
+
k = key(self, *args, **kwargs)
|
|
14
|
+
with lock(self):
|
|
15
|
+
p = pending.setdefault(self, set())
|
|
16
|
+
cond(self).wait_for(lambda: k not in p)
|
|
17
|
+
try:
|
|
18
|
+
return c[k]
|
|
19
|
+
except KeyError:
|
|
20
|
+
p.add(k)
|
|
21
|
+
try:
|
|
22
|
+
v = method(self, *args, **kwargs)
|
|
23
|
+
with lock(self):
|
|
24
|
+
try:
|
|
25
|
+
c[k] = v
|
|
26
|
+
except ValueError:
|
|
27
|
+
pass # value too large
|
|
28
|
+
return v
|
|
29
|
+
finally:
|
|
30
|
+
with lock(self):
|
|
31
|
+
pending[self].remove(k)
|
|
32
|
+
cond(self).notify_all()
|
|
33
|
+
|
|
34
|
+
def cache_clear(self):
|
|
35
|
+
c = cache(self)
|
|
36
|
+
if c is not None:
|
|
37
|
+
with lock(self):
|
|
38
|
+
c.clear()
|
|
39
|
+
|
|
40
|
+
wrapper.cache_clear = cache_clear
|
|
41
|
+
return wrapper
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _cachedmethod_locked(method, cache, key, lock):
|
|
45
|
+
def wrapper(self, *args, **kwargs):
|
|
46
|
+
c = cache(self)
|
|
47
|
+
if c is None:
|
|
48
|
+
return method(self, *args, **kwargs)
|
|
49
|
+
k = key(self, *args, **kwargs)
|
|
50
|
+
with lock(self):
|
|
51
|
+
try:
|
|
52
|
+
return c[k]
|
|
53
|
+
except KeyError:
|
|
54
|
+
pass # key not found
|
|
55
|
+
v = method(self, *args, **kwargs)
|
|
56
|
+
# in case of a race, prefer the item already in the cache
|
|
57
|
+
with lock(self):
|
|
58
|
+
try:
|
|
59
|
+
return c.setdefault(k, v)
|
|
60
|
+
except ValueError:
|
|
61
|
+
return v # value too large
|
|
62
|
+
|
|
63
|
+
def cache_clear(self):
|
|
64
|
+
c = cache(self)
|
|
65
|
+
if c is not None:
|
|
66
|
+
with lock(self):
|
|
67
|
+
c.clear()
|
|
68
|
+
|
|
69
|
+
wrapper.cache_clear = cache_clear
|
|
70
|
+
return wrapper
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _cachedmethod_unlocked(method, cache, key):
|
|
74
|
+
def wrapper(self, *args, **kwargs):
|
|
75
|
+
c = cache(self)
|
|
76
|
+
if c is None:
|
|
77
|
+
return method(self, *args, **kwargs)
|
|
78
|
+
k = key(self, *args, **kwargs)
|
|
79
|
+
try:
|
|
80
|
+
return c[k]
|
|
81
|
+
except KeyError:
|
|
82
|
+
pass # key not found
|
|
83
|
+
v = method(self, *args, **kwargs)
|
|
84
|
+
try:
|
|
85
|
+
c[k] = v
|
|
86
|
+
except ValueError:
|
|
87
|
+
pass # value too large
|
|
88
|
+
return v
|
|
89
|
+
|
|
90
|
+
def cache_clear(self):
|
|
91
|
+
c = cache(self)
|
|
92
|
+
if c is not None:
|
|
93
|
+
c.clear()
|
|
94
|
+
|
|
95
|
+
wrapper.cache_clear = cache_clear
|
|
96
|
+
return wrapper
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _cachedmethod_wrapper(func, cache, key, lock=None, cond=None):
|
|
100
|
+
if cond is not None and lock is not None:
|
|
101
|
+
wrapper = _cachedmethod_condition(func, cache, key, lock, cond)
|
|
102
|
+
elif cond is not None:
|
|
103
|
+
wrapper = _cachedmethod_condition(func, cache, key, cond, cond)
|
|
104
|
+
elif lock is not None:
|
|
105
|
+
wrapper = _cachedmethod_locked(func, cache, key, lock)
|
|
106
|
+
else:
|
|
107
|
+
wrapper = _cachedmethod_unlocked(func, cache, key)
|
|
108
|
+
return wrapper
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
"""`functools.lru_cache` compatible memoizing function decorators."""
|
|
2
2
|
|
|
3
|
-
__all__ = ("fifo_cache", "lfu_cache", "lru_cache", "
|
|
3
|
+
__all__ = ("fifo_cache", "lfu_cache", "lru_cache", "rr_cache", "ttl_cache")
|
|
4
4
|
|
|
5
|
+
import functools
|
|
5
6
|
import math
|
|
6
7
|
import random
|
|
7
8
|
import time
|
|
8
9
|
|
|
9
10
|
try:
|
|
10
|
-
from threading import
|
|
11
|
+
from threading import Condition
|
|
11
12
|
except ImportError: # pragma: no cover
|
|
12
|
-
from dummy_threading import
|
|
13
|
+
from dummy_threading import Condition
|
|
13
14
|
|
|
14
|
-
from . import FIFOCache, LFUCache, LRUCache,
|
|
15
|
+
from . import FIFOCache, LFUCache, LRUCache, RRCache, TTLCache
|
|
15
16
|
from . import cached
|
|
16
17
|
from . import keys
|
|
17
18
|
|
|
@@ -28,7 +29,7 @@ class _UnboundTTLCache(TTLCache):
|
|
|
28
29
|
def _cache(cache, maxsize, typed):
|
|
29
30
|
def decorator(func):
|
|
30
31
|
key = keys.typedkey if typed else keys.hashkey
|
|
31
|
-
wrapper = cached(cache=cache, key=key,
|
|
32
|
+
wrapper = cached(cache=cache, key=key, condition=Condition(), info=True)(func)
|
|
32
33
|
wrapper.cache_parameters = lambda: {"maxsize": maxsize, "typed": typed}
|
|
33
34
|
return wrapper
|
|
34
35
|
|
|
@@ -77,23 +78,6 @@ def lru_cache(maxsize=128, typed=False):
|
|
|
77
78
|
return _cache(LRUCache(maxsize), maxsize, typed)
|
|
78
79
|
|
|
79
80
|
|
|
80
|
-
def mru_cache(maxsize=128, typed=False):
|
|
81
|
-
"""Decorator to wrap a function with a memoizing callable that saves
|
|
82
|
-
up to `maxsize` results based on a Most Recently Used (MRU)
|
|
83
|
-
algorithm.
|
|
84
|
-
"""
|
|
85
|
-
from warnings import warn
|
|
86
|
-
|
|
87
|
-
warn("@mru_cache is deprecated", DeprecationWarning, stacklevel=2)
|
|
88
|
-
|
|
89
|
-
if maxsize is None:
|
|
90
|
-
return _cache({}, None, typed)
|
|
91
|
-
elif callable(maxsize):
|
|
92
|
-
return _cache(MRUCache(128), 128, typed)(maxsize)
|
|
93
|
-
else:
|
|
94
|
-
return _cache(MRUCache(maxsize), maxsize, typed)
|
|
95
|
-
|
|
96
|
-
|
|
97
81
|
def rr_cache(maxsize=128, choice=random.choice, typed=False):
|
|
98
82
|
"""Decorator to wrap a function with a memoizing callable that saves
|
|
99
83
|
up to `maxsize` results based on a Random Replacement (RR)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: cachetools
|
|
3
|
-
Version:
|
|
3
|
+
Version: 6.0.0b2
|
|
4
4
|
Summary: Extensible memoizing collections and decorators
|
|
5
5
|
Home-page: https://github.com/tkem/cachetools/
|
|
6
6
|
Author: Thomas Kemmer
|
|
@@ -13,8 +13,6 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
13
13
|
Classifier: Operating System :: OS Independent
|
|
14
14
|
Classifier: Programming Language :: Python
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
18
16
|
Classifier: Programming Language :: Python :: 3.9
|
|
19
17
|
Classifier: Programming Language :: Python :: 3.10
|
|
20
18
|
Classifier: Programming Language :: Python :: 3.11
|
|
@@ -23,6 +21,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
23
21
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
22
|
Requires-Python: >=3.7
|
|
25
23
|
License-File: LICENSE
|
|
24
|
+
Dynamic: license-file
|
|
26
25
|
|
|
27
26
|
cachetools
|
|
28
27
|
========================================================================
|
|
@@ -9,7 +9,8 @@ tox.ini
|
|
|
9
9
|
docs/conf.py
|
|
10
10
|
docs/index.rst
|
|
11
11
|
src/cachetools/__init__.py
|
|
12
|
-
src/cachetools/
|
|
12
|
+
src/cachetools/_cached.py
|
|
13
|
+
src/cachetools/_cachedmethod.py
|
|
13
14
|
src/cachetools/func.py
|
|
14
15
|
src/cachetools/keys.py
|
|
15
16
|
src/cachetools.egg-info/PKG-INFO
|
|
@@ -25,7 +26,6 @@ tests/test_func.py
|
|
|
25
26
|
tests/test_keys.py
|
|
26
27
|
tests/test_lfu.py
|
|
27
28
|
tests/test_lru.py
|
|
28
|
-
tests/test_mru.py
|
|
29
29
|
tests/test_rr.py
|
|
30
30
|
tests/test_tlru.py
|
|
31
31
|
tests/test_ttl.py
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import unittest
|
|
2
|
+
import warnings
|
|
2
3
|
|
|
3
4
|
import cachetools
|
|
4
5
|
import cachetools.keys
|
|
@@ -15,6 +16,19 @@ class CountedLock:
|
|
|
15
16
|
pass
|
|
16
17
|
|
|
17
18
|
|
|
19
|
+
class CountedCondition(CountedLock):
|
|
20
|
+
def __init__(self):
|
|
21
|
+
CountedLock.__init__(self)
|
|
22
|
+
self.wait_count = 0
|
|
23
|
+
self.notify_count = 0
|
|
24
|
+
|
|
25
|
+
def wait_for(self, predicate):
|
|
26
|
+
self.wait_count += 1
|
|
27
|
+
|
|
28
|
+
def notify_all(self):
|
|
29
|
+
self.notify_count += 1
|
|
30
|
+
|
|
31
|
+
|
|
18
32
|
class DecoratorTestMixin:
|
|
19
33
|
def cache(self, minsize):
|
|
20
34
|
raise NotImplementedError
|
|
@@ -95,6 +109,45 @@ class DecoratorTestMixin:
|
|
|
95
109
|
self.assertEqual(wrapper(1), 1)
|
|
96
110
|
self.assertEqual(lock.count, 5)
|
|
97
111
|
|
|
112
|
+
def test_decorator_condition(self):
|
|
113
|
+
cache = self.cache(2)
|
|
114
|
+
lock = cond = CountedCondition()
|
|
115
|
+
wrapper = cachetools.cached(cache, condition=cond)(self.func)
|
|
116
|
+
|
|
117
|
+
self.assertEqual(len(cache), 0)
|
|
118
|
+
self.assertEqual(wrapper(0), 0)
|
|
119
|
+
self.assertEqual(lock.count, 3)
|
|
120
|
+
self.assertEqual(cond.wait_count, 1)
|
|
121
|
+
self.assertEqual(cond.notify_count, 1)
|
|
122
|
+
self.assertEqual(wrapper(1), 1)
|
|
123
|
+
self.assertEqual(lock.count, 6)
|
|
124
|
+
self.assertEqual(cond.wait_count, 2)
|
|
125
|
+
self.assertEqual(cond.notify_count, 2)
|
|
126
|
+
self.assertEqual(wrapper(1), 1)
|
|
127
|
+
self.assertEqual(lock.count, 7)
|
|
128
|
+
self.assertEqual(cond.wait_count, 3)
|
|
129
|
+
self.assertEqual(cond.notify_count, 2)
|
|
130
|
+
|
|
131
|
+
def test_decorator_lock_condition(self):
|
|
132
|
+
cache = self.cache(2)
|
|
133
|
+
lock = CountedLock()
|
|
134
|
+
cond = CountedCondition()
|
|
135
|
+
wrapper = cachetools.cached(cache, lock=lock, condition=cond)(self.func)
|
|
136
|
+
|
|
137
|
+
self.assertEqual(len(cache), 0)
|
|
138
|
+
self.assertEqual(wrapper(0), 0)
|
|
139
|
+
self.assertEqual(lock.count, 3)
|
|
140
|
+
self.assertEqual(cond.wait_count, 1)
|
|
141
|
+
self.assertEqual(cond.notify_count, 1)
|
|
142
|
+
self.assertEqual(wrapper(1), 1)
|
|
143
|
+
self.assertEqual(lock.count, 6)
|
|
144
|
+
self.assertEqual(cond.wait_count, 2)
|
|
145
|
+
self.assertEqual(cond.notify_count, 2)
|
|
146
|
+
self.assertEqual(wrapper(1), 1)
|
|
147
|
+
self.assertEqual(lock.count, 7)
|
|
148
|
+
self.assertEqual(cond.wait_count, 3)
|
|
149
|
+
self.assertEqual(cond.notify_count, 2)
|
|
150
|
+
|
|
98
151
|
def test_decorator_wrapped(self):
|
|
99
152
|
cache = self.cache(2)
|
|
100
153
|
wrapper = cachetools.cached(cache)(self.func)
|
|
@@ -145,6 +198,17 @@ class DecoratorTestMixin:
|
|
|
145
198
|
self.assertEqual(len(cache), 0)
|
|
146
199
|
self.assertEqual(lock.count, 3)
|
|
147
200
|
|
|
201
|
+
def test_decorator_clear_condition(self):
|
|
202
|
+
cache = self.cache(2)
|
|
203
|
+
lock = cond = CountedCondition()
|
|
204
|
+
wrapper = cachetools.cached(cache, condition=cond)(self.func)
|
|
205
|
+
self.assertEqual(wrapper(0), 0)
|
|
206
|
+
self.assertEqual(len(cache), 1)
|
|
207
|
+
self.assertEqual(lock.count, 3)
|
|
208
|
+
wrapper.cache_clear()
|
|
209
|
+
self.assertEqual(len(cache), 0)
|
|
210
|
+
self.assertEqual(lock.count, 4)
|
|
211
|
+
|
|
148
212
|
|
|
149
213
|
class CacheWrapperTest(unittest.TestCase, DecoratorTestMixin):
|
|
150
214
|
def cache(self, minsize):
|
|
@@ -188,6 +252,69 @@ class CacheWrapperTest(unittest.TestCase, DecoratorTestMixin):
|
|
|
188
252
|
self.assertEqual(wrapper.cache_info(), (0, 0, 2, 0))
|
|
189
253
|
self.assertEqual(lock.count, 11)
|
|
190
254
|
|
|
255
|
+
def test_decorator_lock_info_deprecated(self):
|
|
256
|
+
cache = self.cache(2)
|
|
257
|
+
key = cachetools.keys.hashkey
|
|
258
|
+
lock = CountedLock()
|
|
259
|
+
with warnings.catch_warnings(record=True) as w:
|
|
260
|
+
warnings.simplefilter("always")
|
|
261
|
+
# passing `ìnfo` as positional parameter is deprecated
|
|
262
|
+
wrapper = cachetools.cached(cache, key, lock, True)(self.func)
|
|
263
|
+
self.assertEqual(len(w), 1)
|
|
264
|
+
self.assertIs(w[0].category, DeprecationWarning)
|
|
265
|
+
self.assertEqual(wrapper.cache_info(), (0, 0, 2, 0))
|
|
266
|
+
|
|
267
|
+
def test_decorator_condition_info(self):
|
|
268
|
+
cache = self.cache(2)
|
|
269
|
+
lock = cond = CountedCondition()
|
|
270
|
+
wrapper = cachetools.cached(cache, condition=cond, info=True)(self.func)
|
|
271
|
+
self.assertEqual(wrapper.cache_info(), (0, 0, 2, 0))
|
|
272
|
+
self.assertEqual(lock.count, 1)
|
|
273
|
+
self.assertEqual(wrapper(0), 0)
|
|
274
|
+
self.assertEqual(lock.count, 4)
|
|
275
|
+
self.assertEqual(wrapper.cache_info(), (0, 1, 2, 1))
|
|
276
|
+
self.assertEqual(lock.count, 5)
|
|
277
|
+
self.assertEqual(wrapper(1), 1)
|
|
278
|
+
self.assertEqual(lock.count, 8)
|
|
279
|
+
self.assertEqual(wrapper.cache_info(), (0, 2, 2, 2))
|
|
280
|
+
self.assertEqual(lock.count, 9)
|
|
281
|
+
self.assertEqual(wrapper(0), 0)
|
|
282
|
+
self.assertEqual(lock.count, 10)
|
|
283
|
+
self.assertEqual(wrapper.cache_info(), (1, 2, 2, 2))
|
|
284
|
+
self.assertEqual(lock.count, 11)
|
|
285
|
+
wrapper.cache_clear()
|
|
286
|
+
self.assertEqual(lock.count, 12)
|
|
287
|
+
self.assertEqual(len(cache), 0)
|
|
288
|
+
self.assertEqual(wrapper.cache_info(), (0, 0, 2, 0))
|
|
289
|
+
self.assertEqual(lock.count, 13)
|
|
290
|
+
|
|
291
|
+
def test_decorator_lock_condition_info(self):
|
|
292
|
+
cache = self.cache(2)
|
|
293
|
+
lock = CountedLock()
|
|
294
|
+
cond = CountedCondition()
|
|
295
|
+
wrapper = cachetools.cached(cache, lock=lock, condition=cond, info=True)(
|
|
296
|
+
self.func
|
|
297
|
+
)
|
|
298
|
+
self.assertEqual(wrapper.cache_info(), (0, 0, 2, 0))
|
|
299
|
+
self.assertEqual(lock.count, 1)
|
|
300
|
+
self.assertEqual(wrapper(0), 0)
|
|
301
|
+
self.assertEqual(lock.count, 4)
|
|
302
|
+
self.assertEqual(wrapper.cache_info(), (0, 1, 2, 1))
|
|
303
|
+
self.assertEqual(lock.count, 5)
|
|
304
|
+
self.assertEqual(wrapper(1), 1)
|
|
305
|
+
self.assertEqual(lock.count, 8)
|
|
306
|
+
self.assertEqual(wrapper.cache_info(), (0, 2, 2, 2))
|
|
307
|
+
self.assertEqual(lock.count, 9)
|
|
308
|
+
self.assertEqual(wrapper(0), 0)
|
|
309
|
+
self.assertEqual(lock.count, 10)
|
|
310
|
+
self.assertEqual(wrapper.cache_info(), (1, 2, 2, 2))
|
|
311
|
+
self.assertEqual(lock.count, 11)
|
|
312
|
+
wrapper.cache_clear()
|
|
313
|
+
self.assertEqual(lock.count, 12)
|
|
314
|
+
self.assertEqual(len(cache), 0)
|
|
315
|
+
self.assertEqual(wrapper.cache_info(), (0, 0, 2, 0))
|
|
316
|
+
self.assertEqual(lock.count, 13)
|
|
317
|
+
|
|
191
318
|
def test_zero_size_cache_decorator(self):
|
|
192
319
|
cache = self.cache(0)
|
|
193
320
|
wrapper = cachetools.cached(cache)(self.func)
|
|
@@ -206,6 +333,16 @@ class CacheWrapperTest(unittest.TestCase, DecoratorTestMixin):
|
|
|
206
333
|
self.assertEqual(len(cache), 0)
|
|
207
334
|
self.assertEqual(lock.count, 2)
|
|
208
335
|
|
|
336
|
+
def test_zero_size_cache_decorator_condition(self):
|
|
337
|
+
cache = self.cache(0)
|
|
338
|
+
lock = cond = CountedCondition()
|
|
339
|
+
wrapper = cachetools.cached(cache, condition=cond)(self.func)
|
|
340
|
+
|
|
341
|
+
self.assertEqual(len(cache), 0)
|
|
342
|
+
self.assertEqual(wrapper(0), 0)
|
|
343
|
+
self.assertEqual(len(cache), 0)
|
|
344
|
+
self.assertEqual(lock.count, 3)
|
|
345
|
+
|
|
209
346
|
def test_zero_size_cache_decorator_info(self):
|
|
210
347
|
cache = self.cache(0)
|
|
211
348
|
wrapper = cachetools.cached(cache, info=True)(self.func)
|
|
@@ -214,6 +351,20 @@ class CacheWrapperTest(unittest.TestCase, DecoratorTestMixin):
|
|
|
214
351
|
self.assertEqual(wrapper(0), 0)
|
|
215
352
|
self.assertEqual(wrapper.cache_info(), (0, 1, 0, 0))
|
|
216
353
|
|
|
354
|
+
def test_zero_size_cache_decorator_lock_info(self):
|
|
355
|
+
cache = self.cache(0)
|
|
356
|
+
lock = CountedLock()
|
|
357
|
+
wrapper = cachetools.cached(cache, lock=lock, info=True)(self.func)
|
|
358
|
+
|
|
359
|
+
self.assertEqual(len(cache), 0)
|
|
360
|
+
self.assertEqual(wrapper.cache_info(), (0, 0, 0, 0))
|
|
361
|
+
self.assertEqual(lock.count, 1)
|
|
362
|
+
self.assertEqual(wrapper(0), 0)
|
|
363
|
+
self.assertEqual(len(cache), 0)
|
|
364
|
+
self.assertEqual(lock.count, 3)
|
|
365
|
+
self.assertEqual(wrapper.cache_info(), (0, 1, 0, 0))
|
|
366
|
+
self.assertEqual(lock.count, 4)
|
|
367
|
+
|
|
217
368
|
|
|
218
369
|
class DictWrapperTest(unittest.TestCase, DecoratorTestMixin):
|
|
219
370
|
def cache(self, minsize):
|
|
@@ -25,18 +25,44 @@ class Locked:
|
|
|
25
25
|
def __init__(self, cache):
|
|
26
26
|
self.cache = cache
|
|
27
27
|
self.count = 0
|
|
28
|
+
self.lock_count = 0
|
|
28
29
|
|
|
29
30
|
@cachedmethod(lambda self: self.cache, lock=lambda self: self)
|
|
30
31
|
def get(self, value):
|
|
31
|
-
|
|
32
|
+
count = self.count
|
|
33
|
+
self.count += 1
|
|
34
|
+
return count
|
|
32
35
|
|
|
33
36
|
def __enter__(self):
|
|
34
|
-
self.
|
|
37
|
+
self.lock_count += 1
|
|
35
38
|
|
|
36
39
|
def __exit__(self, *exc):
|
|
37
40
|
pass
|
|
38
41
|
|
|
39
42
|
|
|
43
|
+
class Conditioned(Locked):
|
|
44
|
+
def __init__(self, cache):
|
|
45
|
+
Locked.__init__(self, cache)
|
|
46
|
+
self.wait_count = 0
|
|
47
|
+
self.notify_count = 0
|
|
48
|
+
|
|
49
|
+
@cachedmethod(lambda self: self.cache, condition=lambda self: self)
|
|
50
|
+
def get(self, value):
|
|
51
|
+
return Locked.get.__wrapped__(self, value)
|
|
52
|
+
|
|
53
|
+
@cachedmethod(
|
|
54
|
+
lambda self: self.cache, lock=lambda self: self, condition=lambda self: self
|
|
55
|
+
)
|
|
56
|
+
def get_lock(self, value):
|
|
57
|
+
return Locked.get.__wrapped__(self, value)
|
|
58
|
+
|
|
59
|
+
def wait_for(self, predicate):
|
|
60
|
+
self.wait_count += 1
|
|
61
|
+
|
|
62
|
+
def notify_all(self):
|
|
63
|
+
self.notify_count += 1
|
|
64
|
+
|
|
65
|
+
|
|
40
66
|
class Unhashable:
|
|
41
67
|
def __init__(self, cache):
|
|
42
68
|
self.cache = cache
|
|
@@ -152,29 +178,110 @@ class CachedMethodTest(unittest.TestCase):
|
|
|
152
178
|
def test_locked_dict(self):
|
|
153
179
|
cached = Locked({})
|
|
154
180
|
|
|
155
|
-
self.assertEqual(cached.get(0),
|
|
156
|
-
self.assertEqual(cached.
|
|
157
|
-
self.assertEqual(cached.get(1),
|
|
181
|
+
self.assertEqual(cached.get(0), 0)
|
|
182
|
+
self.assertEqual(cached.lock_count, 2)
|
|
183
|
+
self.assertEqual(cached.get(1), 1)
|
|
184
|
+
self.assertEqual(cached.lock_count, 4)
|
|
185
|
+
self.assertEqual(cached.get(1), 1)
|
|
186
|
+
self.assertEqual(cached.lock_count, 5)
|
|
187
|
+
self.assertEqual(cached.get(1.0), 1)
|
|
188
|
+
self.assertEqual(cached.lock_count, 6)
|
|
189
|
+
self.assertEqual(cached.get(1.0), 1)
|
|
190
|
+
self.assertEqual(cached.lock_count, 7)
|
|
191
|
+
|
|
192
|
+
cached.cache.clear()
|
|
193
|
+
self.assertEqual(cached.get(1), 2)
|
|
194
|
+
self.assertEqual(cached.lock_count, 9)
|
|
195
|
+
|
|
196
|
+
def test_locked_nospace(self):
|
|
197
|
+
cached = Locked(LRUCache(maxsize=0))
|
|
198
|
+
|
|
199
|
+
self.assertEqual(cached.get(0), 0)
|
|
200
|
+
self.assertEqual(cached.lock_count, 2)
|
|
201
|
+
self.assertEqual(cached.get(1), 1)
|
|
202
|
+
self.assertEqual(cached.lock_count, 4)
|
|
203
|
+
self.assertEqual(cached.get(1), 2)
|
|
204
|
+
self.assertEqual(cached.lock_count, 6)
|
|
158
205
|
self.assertEqual(cached.get(1.0), 3)
|
|
159
|
-
self.assertEqual(cached.
|
|
206
|
+
self.assertEqual(cached.lock_count, 8)
|
|
207
|
+
self.assertEqual(cached.get(1.0), 4)
|
|
208
|
+
self.assertEqual(cached.lock_count, 10)
|
|
160
209
|
|
|
161
210
|
def test_locked_nocache(self):
|
|
162
211
|
cached = Locked(None)
|
|
163
212
|
|
|
164
213
|
self.assertEqual(cached.get(0), 0)
|
|
165
|
-
self.assertEqual(cached.get(1),
|
|
166
|
-
self.assertEqual(cached.get(1),
|
|
167
|
-
self.assertEqual(cached.get(1.0),
|
|
168
|
-
self.assertEqual(cached.get(1.0),
|
|
214
|
+
self.assertEqual(cached.get(1), 1)
|
|
215
|
+
self.assertEqual(cached.get(1), 2)
|
|
216
|
+
self.assertEqual(cached.get(1.0), 3)
|
|
217
|
+
self.assertEqual(cached.get(1.0), 4)
|
|
218
|
+
self.assertEqual(cached.lock_count, 0)
|
|
169
219
|
|
|
170
|
-
def
|
|
171
|
-
cached =
|
|
220
|
+
def test_condition_dict(self):
|
|
221
|
+
cached = Conditioned({})
|
|
172
222
|
|
|
173
|
-
self.assertEqual(cached.get(0),
|
|
174
|
-
self.assertEqual(cached.
|
|
175
|
-
self.assertEqual(cached.
|
|
176
|
-
self.assertEqual(cached.
|
|
177
|
-
self.assertEqual(cached.get(1
|
|
223
|
+
self.assertEqual(cached.get(0), 0)
|
|
224
|
+
self.assertEqual(cached.lock_count, 3)
|
|
225
|
+
self.assertEqual(cached.wait_count, 1)
|
|
226
|
+
self.assertEqual(cached.notify_count, 1)
|
|
227
|
+
self.assertEqual(cached.get(1), 1)
|
|
228
|
+
self.assertEqual(cached.lock_count, 6)
|
|
229
|
+
self.assertEqual(cached.wait_count, 2)
|
|
230
|
+
self.assertEqual(cached.notify_count, 2)
|
|
231
|
+
self.assertEqual(cached.get(1), 1)
|
|
232
|
+
self.assertEqual(cached.lock_count, 7)
|
|
233
|
+
self.assertEqual(cached.wait_count, 3)
|
|
234
|
+
self.assertEqual(cached.notify_count, 2)
|
|
235
|
+
self.assertEqual(cached.get(1.0), 1)
|
|
236
|
+
self.assertEqual(cached.lock_count, 8)
|
|
237
|
+
self.assertEqual(cached.wait_count, 4)
|
|
238
|
+
self.assertEqual(cached.notify_count, 2)
|
|
239
|
+
self.assertEqual(cached.get(1.0), 1)
|
|
240
|
+
self.assertEqual(cached.lock_count, 9)
|
|
241
|
+
self.assertEqual(cached.wait_count, 5)
|
|
242
|
+
self.assertEqual(cached.notify_count, 2)
|
|
243
|
+
|
|
244
|
+
cached.cache.clear()
|
|
245
|
+
self.assertEqual(cached.get(1), 2)
|
|
246
|
+
self.assertEqual(cached.lock_count, 12)
|
|
247
|
+
self.assertEqual(cached.wait_count, 6)
|
|
248
|
+
self.assertEqual(cached.notify_count, 3)
|
|
249
|
+
|
|
250
|
+
def test_condition_nospace(self):
|
|
251
|
+
cached = Conditioned(LRUCache(maxsize=0))
|
|
252
|
+
|
|
253
|
+
self.assertEqual(cached.get(0), 0)
|
|
254
|
+
self.assertEqual(cached.lock_count, 3)
|
|
255
|
+
self.assertEqual(cached.wait_count, 1)
|
|
256
|
+
self.assertEqual(cached.notify_count, 1)
|
|
257
|
+
self.assertEqual(cached.get(1), 1)
|
|
258
|
+
self.assertEqual(cached.lock_count, 6)
|
|
259
|
+
self.assertEqual(cached.wait_count, 2)
|
|
260
|
+
self.assertEqual(cached.notify_count, 2)
|
|
261
|
+
self.assertEqual(cached.get(1), 2)
|
|
262
|
+
self.assertEqual(cached.lock_count, 9)
|
|
263
|
+
self.assertEqual(cached.wait_count, 3)
|
|
264
|
+
self.assertEqual(cached.notify_count, 3)
|
|
265
|
+
self.assertEqual(cached.get(1.0), 3)
|
|
266
|
+
self.assertEqual(cached.lock_count, 12)
|
|
267
|
+
self.assertEqual(cached.wait_count, 4)
|
|
268
|
+
self.assertEqual(cached.notify_count, 4)
|
|
269
|
+
self.assertEqual(cached.get(1.0), 4)
|
|
270
|
+
self.assertEqual(cached.lock_count, 15)
|
|
271
|
+
self.assertEqual(cached.wait_count, 5)
|
|
272
|
+
self.assertEqual(cached.notify_count, 5)
|
|
273
|
+
|
|
274
|
+
def test_condition_nocache(self):
|
|
275
|
+
cached = Conditioned(None)
|
|
276
|
+
|
|
277
|
+
self.assertEqual(cached.get(0), 0)
|
|
278
|
+
self.assertEqual(cached.get(1), 1)
|
|
279
|
+
self.assertEqual(cached.get(1), 2)
|
|
280
|
+
self.assertEqual(cached.get(1.0), 3)
|
|
281
|
+
self.assertEqual(cached.get(1.0), 4)
|
|
282
|
+
self.assertEqual(cached.lock_count, 0)
|
|
283
|
+
self.assertEqual(cached.wait_count, 0)
|
|
284
|
+
self.assertEqual(cached.notify_count, 0)
|
|
178
285
|
|
|
179
286
|
def test_unhashable(self):
|
|
180
287
|
cached = Unhashable(LRUCache(maxsize=0))
|
|
@@ -226,9 +333,20 @@ class CachedMethodTest(unittest.TestCase):
|
|
|
226
333
|
cache = {}
|
|
227
334
|
cached = Locked(cache)
|
|
228
335
|
|
|
229
|
-
self.assertEqual(cached.get(0),
|
|
336
|
+
self.assertEqual(cached.get(0), 0)
|
|
337
|
+
self.assertEqual(len(cache), 1)
|
|
338
|
+
self.assertEqual(cached.lock_count, 2)
|
|
339
|
+
cached.get.cache_clear(cached)
|
|
340
|
+
self.assertEqual(len(cache), 0)
|
|
341
|
+
self.assertEqual(cached.lock_count, 3)
|
|
342
|
+
|
|
343
|
+
def test_clear_condition(self):
|
|
344
|
+
cache = {}
|
|
345
|
+
cached = Conditioned(cache)
|
|
346
|
+
|
|
347
|
+
self.assertEqual(cached.get(0), 0)
|
|
230
348
|
self.assertEqual(len(cache), 1)
|
|
231
|
-
self.assertEqual(cached.
|
|
349
|
+
self.assertEqual(cached.lock_count, 3)
|
|
232
350
|
cached.get.cache_clear(cached)
|
|
233
351
|
self.assertEqual(len(cache), 0)
|
|
234
|
-
self.assertEqual(cached.
|
|
352
|
+
self.assertEqual(cached.lock_count, 4)
|
|
@@ -111,18 +111,6 @@ class LRUDecoratorTest(unittest.TestCase, DecoratorTestMixin):
|
|
|
111
111
|
DECORATOR = staticmethod(cachetools.func.lru_cache)
|
|
112
112
|
|
|
113
113
|
|
|
114
|
-
class MRUDecoratorTest(unittest.TestCase, DecoratorTestMixin):
|
|
115
|
-
def decorator(self, maxsize, **kwargs):
|
|
116
|
-
import warnings
|
|
117
|
-
|
|
118
|
-
with warnings.catch_warnings(record=True) as w:
|
|
119
|
-
warnings.simplefilter("always")
|
|
120
|
-
d = cachetools.func.mru_cache(maxsize, **kwargs)
|
|
121
|
-
self.assertNotEqual(len(w), 0)
|
|
122
|
-
self.assertIs(w[0].category, DeprecationWarning)
|
|
123
|
-
return d
|
|
124
|
-
|
|
125
|
-
|
|
126
114
|
class RRDecoratorTest(unittest.TestCase, DecoratorTestMixin):
|
|
127
115
|
DECORATOR = staticmethod(cachetools.func.rr_cache)
|
|
128
116
|
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import unittest
|
|
2
|
-
import warnings
|
|
3
|
-
|
|
4
|
-
from cachetools import MRUCache
|
|
5
|
-
|
|
6
|
-
from . import CacheTestMixin
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class MRUCacheTest(unittest.TestCase, CacheTestMixin):
|
|
10
|
-
# TODO: method to create cache that can be overridden
|
|
11
|
-
Cache = MRUCache
|
|
12
|
-
|
|
13
|
-
def test_evict__writes_only(self):
|
|
14
|
-
with warnings.catch_warnings(record=True) as w:
|
|
15
|
-
warnings.simplefilter("always")
|
|
16
|
-
cache = MRUCache(maxsize=2)
|
|
17
|
-
self.assertEqual(len(w), 1)
|
|
18
|
-
self.assertIs(w[0].category, DeprecationWarning)
|
|
19
|
-
|
|
20
|
-
cache[1] = 1
|
|
21
|
-
cache[2] = 2
|
|
22
|
-
cache[3] = 3 # Evicts 1 because nothing's been used yet
|
|
23
|
-
|
|
24
|
-
assert len(cache) == 2
|
|
25
|
-
assert 1 not in cache, "Wrong key was evicted. Should have been '1'."
|
|
26
|
-
assert 2 in cache
|
|
27
|
-
assert 3 in cache
|
|
28
|
-
|
|
29
|
-
def test_evict__with_access(self):
|
|
30
|
-
with warnings.catch_warnings(record=True) as w:
|
|
31
|
-
warnings.simplefilter("always")
|
|
32
|
-
cache = MRUCache(maxsize=2)
|
|
33
|
-
self.assertEqual(len(w), 1)
|
|
34
|
-
self.assertIs(w[0].category, DeprecationWarning)
|
|
35
|
-
|
|
36
|
-
cache[1] = 1
|
|
37
|
-
cache[2] = 2
|
|
38
|
-
cache[1]
|
|
39
|
-
cache[2]
|
|
40
|
-
cache[3] = 3 # Evicts 2
|
|
41
|
-
assert 2 not in cache, "Wrong key was evicted. Should have been '2'."
|
|
42
|
-
assert 1 in cache
|
|
43
|
-
assert 3 in cache
|
|
44
|
-
|
|
45
|
-
def test_evict__with_delete(self):
|
|
46
|
-
with warnings.catch_warnings(record=True) as w:
|
|
47
|
-
warnings.simplefilter("always")
|
|
48
|
-
cache = MRUCache(maxsize=2)
|
|
49
|
-
self.assertEqual(len(w), 1)
|
|
50
|
-
self.assertIs(w[0].category, DeprecationWarning)
|
|
51
|
-
|
|
52
|
-
cache[1] = 1
|
|
53
|
-
cache[2] = 2
|
|
54
|
-
del cache[2]
|
|
55
|
-
cache[3] = 3 # Doesn't evict anything because we just deleted 2
|
|
56
|
-
|
|
57
|
-
assert 2 not in cache
|
|
58
|
-
assert 1 in cache
|
|
59
|
-
|
|
60
|
-
cache[4] = 4 # Should evict 1 as we just accessed it with __contains__
|
|
61
|
-
assert 1 not in cache
|
|
62
|
-
assert 3 in cache
|
|
63
|
-
assert 4 in cache
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|