cachetools 7.2.5__tar.gz → 7.2.6__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-7.2.5 → cachetools-7.2.6}/CHANGELOG.rst +6 -0
- {cachetools-7.2.5/src/cachetools.egg-info → cachetools-7.2.6}/PKG-INFO +1 -1
- {cachetools-7.2.5 → cachetools-7.2.6}/src/cachetools/__init__.py +36 -22
- {cachetools-7.2.5 → cachetools-7.2.6}/src/cachetools/_cached.py +6 -2
- {cachetools-7.2.5 → cachetools-7.2.6}/src/cachetools/_cachedmethod.py +6 -2
- {cachetools-7.2.5 → cachetools-7.2.6}/src/cachetools/keys.py +22 -11
- {cachetools-7.2.5 → cachetools-7.2.6/src/cachetools.egg-info}/PKG-INFO +1 -1
- {cachetools-7.2.5 → cachetools-7.2.6}/tests/test_cached.py +8 -8
- {cachetools-7.2.5 → cachetools-7.2.6}/tests/test_cachedmethod.py +26 -26
- {cachetools-7.2.5 → cachetools-7.2.6}/tests/test_keys.py +1 -2
- {cachetools-7.2.5 → cachetools-7.2.6}/tests/test_threading.py +60 -11
- {cachetools-7.2.5 → cachetools-7.2.6}/LICENSE +0 -0
- {cachetools-7.2.5 → cachetools-7.2.6}/MANIFEST.in +0 -0
- {cachetools-7.2.5 → cachetools-7.2.6}/README.rst +0 -0
- {cachetools-7.2.5 → cachetools-7.2.6}/docs/conf.py +0 -0
- {cachetools-7.2.5 → cachetools-7.2.6}/docs/index.rst +0 -0
- {cachetools-7.2.5 → cachetools-7.2.6}/pyproject.toml +0 -0
- {cachetools-7.2.5 → cachetools-7.2.6}/setup.cfg +0 -0
- {cachetools-7.2.5 → cachetools-7.2.6}/src/cachetools/__init__.pyi +0 -0
- {cachetools-7.2.5 → cachetools-7.2.6}/src/cachetools/_descriptor.py +0 -0
- {cachetools-7.2.5 → cachetools-7.2.6}/src/cachetools/func.py +0 -0
- {cachetools-7.2.5 → cachetools-7.2.6}/src/cachetools/func.pyi +0 -0
- {cachetools-7.2.5 → cachetools-7.2.6}/src/cachetools/keys.pyi +0 -0
- {cachetools-7.2.5 → cachetools-7.2.6}/src/cachetools/py.typed +0 -0
- {cachetools-7.2.5 → cachetools-7.2.6}/src/cachetools.egg-info/SOURCES.txt +0 -0
- {cachetools-7.2.5 → cachetools-7.2.6}/src/cachetools.egg-info/dependency_links.txt +0 -0
- {cachetools-7.2.5 → cachetools-7.2.6}/src/cachetools.egg-info/top_level.txt +0 -0
- {cachetools-7.2.5 → cachetools-7.2.6}/tests/__init__.py +0 -0
- {cachetools-7.2.5 → cachetools-7.2.6}/tests/test_cache.py +0 -0
- {cachetools-7.2.5 → cachetools-7.2.6}/tests/test_classmethod.py +0 -0
- {cachetools-7.2.5 → cachetools-7.2.6}/tests/test_fifo.py +0 -0
- {cachetools-7.2.5 → cachetools-7.2.6}/tests/test_func.py +0 -0
- {cachetools-7.2.5 → cachetools-7.2.6}/tests/test_lfu.py +0 -0
- {cachetools-7.2.5 → cachetools-7.2.6}/tests/test_lru.py +0 -0
- {cachetools-7.2.5 → cachetools-7.2.6}/tests/test_rr.py +0 -0
- {cachetools-7.2.5 → cachetools-7.2.6}/tests/test_tlru.py +0 -0
- {cachetools-7.2.5 → cachetools-7.2.6}/tests/test_ttl.py +0 -0
- {cachetools-7.2.5 → cachetools-7.2.6}/tox.ini +0 -0
|
@@ -12,7 +12,7 @@ __all__ = (
|
|
|
12
12
|
"cachedmethod",
|
|
13
13
|
)
|
|
14
14
|
|
|
15
|
-
__version__ = "7.2.
|
|
15
|
+
__version__ = "7.2.6"
|
|
16
16
|
|
|
17
17
|
import collections
|
|
18
18
|
import collections.abc
|
|
@@ -193,7 +193,9 @@ class FIFOCache(Cache):
|
|
|
193
193
|
except StopIteration:
|
|
194
194
|
raise KeyError("%s is empty" % type(self).__name__) from None
|
|
195
195
|
else:
|
|
196
|
-
|
|
196
|
+
value = Cache.__getitem__(self, key)
|
|
197
|
+
del self[key]
|
|
198
|
+
return (key, value)
|
|
197
199
|
|
|
198
200
|
def clear(self):
|
|
199
201
|
Cache.clear(self)
|
|
@@ -224,7 +226,7 @@ class LFUCache(Cache):
|
|
|
224
226
|
|
|
225
227
|
def __getitem__(self, key, cache_getitem=Cache.__getitem__):
|
|
226
228
|
value = cache_getitem(self, key)
|
|
227
|
-
if key in self: # __missing__ may not store item
|
|
229
|
+
if key in self.__links: # __missing__ may not store item
|
|
228
230
|
self.__touch(key)
|
|
229
231
|
return value
|
|
230
232
|
|
|
@@ -257,7 +259,9 @@ class LFUCache(Cache):
|
|
|
257
259
|
if curr is root:
|
|
258
260
|
raise KeyError("%s is empty" % type(self).__name__) from None
|
|
259
261
|
key = next(iter(curr.keys)) # remove an arbitrary element
|
|
260
|
-
|
|
262
|
+
value = Cache.__getitem__(self, key)
|
|
263
|
+
del self[key]
|
|
264
|
+
return (key, value)
|
|
261
265
|
|
|
262
266
|
def clear(self):
|
|
263
267
|
Cache.clear(self)
|
|
@@ -293,8 +297,10 @@ class LRUCache(Cache):
|
|
|
293
297
|
|
|
294
298
|
def __getitem__(self, key, cache_getitem=Cache.__getitem__):
|
|
295
299
|
value = cache_getitem(self, key)
|
|
296
|
-
|
|
297
|
-
self.
|
|
300
|
+
try:
|
|
301
|
+
self.__order.move_to_end(key)
|
|
302
|
+
except KeyError: # __missing__ may not store item
|
|
303
|
+
pass
|
|
298
304
|
return value
|
|
299
305
|
|
|
300
306
|
def __setitem__(self, key, value, cache_setitem=Cache.__setitem__):
|
|
@@ -312,7 +318,9 @@ class LRUCache(Cache):
|
|
|
312
318
|
except StopIteration:
|
|
313
319
|
raise KeyError("%s is empty" % type(self).__name__) from None
|
|
314
320
|
else:
|
|
315
|
-
|
|
321
|
+
value = Cache.__getitem__(self, key)
|
|
322
|
+
del self[key]
|
|
323
|
+
return (key, value)
|
|
316
324
|
|
|
317
325
|
def clear(self):
|
|
318
326
|
Cache.clear(self)
|
|
@@ -361,7 +369,9 @@ class RRCache(Cache):
|
|
|
361
369
|
if not keys:
|
|
362
370
|
raise KeyError("%s is empty" % type(self).__name__) from None
|
|
363
371
|
key = self.__choice(keys)
|
|
364
|
-
|
|
372
|
+
value = Cache.__getitem__(self, key)
|
|
373
|
+
del self[key]
|
|
374
|
+
return (key, value)
|
|
365
375
|
|
|
366
376
|
def clear(self):
|
|
367
377
|
Cache.clear(self)
|
|
@@ -482,16 +492,16 @@ class TTLCache(_TimedCache):
|
|
|
482
492
|
return self.timer() < link.expires
|
|
483
493
|
|
|
484
494
|
def __getitem__(self, key, cache_getitem=Cache.__getitem__):
|
|
495
|
+
links = self.__links
|
|
485
496
|
try:
|
|
486
|
-
link =
|
|
497
|
+
link = links[key]
|
|
487
498
|
except KeyError:
|
|
488
|
-
|
|
499
|
+
return cache_getitem(self, key)
|
|
500
|
+
links.move_to_end(key)
|
|
501
|
+
if self.timer() < link.expires:
|
|
502
|
+
return cache_getitem(self, key)
|
|
489
503
|
else:
|
|
490
|
-
expired = not (self.timer() < link.expires)
|
|
491
|
-
if expired:
|
|
492
504
|
return self.__missing__(key)
|
|
493
|
-
else:
|
|
494
|
-
return cache_getitem(self, key)
|
|
495
505
|
|
|
496
506
|
def __setitem__(self, key, value, cache_setitem=Cache.__setitem__):
|
|
497
507
|
with self.timer as time:
|
|
@@ -575,7 +585,9 @@ class TTLCache(_TimedCache):
|
|
|
575
585
|
except StopIteration:
|
|
576
586
|
raise KeyError("%s is empty" % type(self).__name__) from None
|
|
577
587
|
else:
|
|
578
|
-
|
|
588
|
+
value = Cache.__getitem__(self, key)
|
|
589
|
+
del self[key]
|
|
590
|
+
return (key, value)
|
|
579
591
|
|
|
580
592
|
def clear(self):
|
|
581
593
|
_TimedCache.clear(self)
|
|
@@ -621,16 +633,16 @@ class TLRUCache(_TimedCache):
|
|
|
621
633
|
return self.timer() < item.expires
|
|
622
634
|
|
|
623
635
|
def __getitem__(self, key, cache_getitem=Cache.__getitem__):
|
|
636
|
+
items = self.__items
|
|
624
637
|
try:
|
|
625
|
-
item =
|
|
638
|
+
item = items[key]
|
|
626
639
|
except KeyError:
|
|
627
|
-
|
|
640
|
+
return cache_getitem(self, key)
|
|
641
|
+
items.move_to_end(key)
|
|
642
|
+
if self.timer() < item.expires:
|
|
643
|
+
return cache_getitem(self, key)
|
|
628
644
|
else:
|
|
629
|
-
expired = not (self.timer() < item.expires)
|
|
630
|
-
if expired:
|
|
631
645
|
return self.__missing__(key)
|
|
632
|
-
else:
|
|
633
|
-
return cache_getitem(self, key)
|
|
634
646
|
|
|
635
647
|
def __setitem__(self, key, value, cache_setitem=Cache.__setitem__):
|
|
636
648
|
with self.timer as time:
|
|
@@ -707,7 +719,9 @@ class TLRUCache(_TimedCache):
|
|
|
707
719
|
except StopIteration:
|
|
708
720
|
raise KeyError("%s is empty" % type(self).__name__) from None
|
|
709
721
|
else:
|
|
710
|
-
|
|
722
|
+
value = Cache.__getitem__(self, key)
|
|
723
|
+
del self[key]
|
|
724
|
+
return (key, value)
|
|
711
725
|
|
|
712
726
|
def clear(self):
|
|
713
727
|
_TimedCache.clear(self)
|
|
@@ -25,7 +25,9 @@ def _condition_info(func, cache, key, lock, cond, info):
|
|
|
25
25
|
nonlocal hits, misses
|
|
26
26
|
k = key(*args, **kwargs)
|
|
27
27
|
with lock:
|
|
28
|
-
cond.wait_for(
|
|
28
|
+
# apparently significantly faster than cond.wait_for(...)
|
|
29
|
+
while k in pending:
|
|
30
|
+
cond.wait()
|
|
29
31
|
try:
|
|
30
32
|
result = cache[k]
|
|
31
33
|
hits += 1
|
|
@@ -158,7 +160,9 @@ def _condition(func, cache, key, lock, cond):
|
|
|
158
160
|
def wrapper(*args, **kwargs):
|
|
159
161
|
k = key(*args, **kwargs)
|
|
160
162
|
with lock:
|
|
161
|
-
cond.wait_for(
|
|
163
|
+
# apparently significantly faster than cond.wait_for(...)
|
|
164
|
+
while k in pending:
|
|
165
|
+
cond.wait()
|
|
162
166
|
try:
|
|
163
167
|
result = cache[k]
|
|
164
168
|
return result
|
|
@@ -79,7 +79,9 @@ def _condition_info(method, cache, key, lock, cond, info):
|
|
|
79
79
|
key = self.cache_key(*args, **kwargs)
|
|
80
80
|
|
|
81
81
|
with lock:
|
|
82
|
-
|
|
82
|
+
# apparently significantly faster than cond.wait_for(...)
|
|
83
|
+
while key in self.__pending:
|
|
84
|
+
cond.wait()
|
|
83
85
|
try:
|
|
84
86
|
result = cache[key]
|
|
85
87
|
self.__hits += 1
|
|
@@ -193,7 +195,9 @@ def _condition(method, cache, key, lock, cond):
|
|
|
193
195
|
c = cache(self)
|
|
194
196
|
k = key(self, *args, **kwargs)
|
|
195
197
|
with lock(self):
|
|
196
|
-
cond
|
|
198
|
+
# apparently significantly faster than cond.wait_for(...)
|
|
199
|
+
while k in pending:
|
|
200
|
+
cond(self).wait()
|
|
197
201
|
try:
|
|
198
202
|
return c[k]
|
|
199
203
|
except KeyError:
|
|
@@ -4,20 +4,31 @@ __all__ = ("hashkey", "methodkey", "typedkey", "typedmethodkey")
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class _HashedTuple(tuple):
|
|
7
|
-
"""A tuple
|
|
8
|
-
per element, since cache decorators will hash the key multiple
|
|
9
|
-
times on a cache miss. See also _HashedSeq in the standard
|
|
10
|
-
library functools implementation.
|
|
7
|
+
"""A tuple subclass used for cache keys.
|
|
11
8
|
|
|
12
|
-
|
|
9
|
+
Unlike _HashedSeq in the standard library functools implementation,
|
|
10
|
+
the hash value is not cached: the built-in tuple hash is faster than
|
|
11
|
+
a Python-level __hash__ that caches it.
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
"""
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
# A Python-level __hash__ that caches the hash costs more than it
|
|
16
|
+
# saves: hashing a 2-tuple three times takes 161 ns as _HashedTuple
|
|
17
|
+
# and 49 ns as a plain tuple (C tuple hash, 18 ns each); a dict
|
|
18
|
+
# lookup keyed by one takes 50 vs 12 ns. Every cache operation
|
|
19
|
+
# hashes the key again (3 __hash__ calls per @cached hit, 12 per
|
|
20
|
+
# evicting miss by cProfile, 11.5% of miss time in __hash__ alone).
|
|
21
|
+
# The cached hash does not pay off even for a key wrapping a 200-int
|
|
22
|
+
# tuple (166 vs 49 ns for three hashes), only for elements whose own
|
|
23
|
+
# __hash__ is slow.
|
|
24
|
+
#
|
|
25
|
+
# __hashvalue = None # default value, set in instance on first use
|
|
26
|
+
#
|
|
27
|
+
# def __hash__(self, hash=tuple.__hash__):
|
|
28
|
+
# hashvalue = self.__hashvalue
|
|
29
|
+
# if hashvalue is None:
|
|
30
|
+
# self.__hashvalue = hashvalue = hash(self)
|
|
31
|
+
# return hashvalue
|
|
21
32
|
|
|
22
33
|
def __add__(self, other, add=tuple.__add__):
|
|
23
34
|
return _HashedTuple(add(self, other))
|
|
@@ -98,15 +98,15 @@ class DecoratorTestMixin(SimplifiedTestCaseProtocol):
|
|
|
98
98
|
self.assertEqual(len(cache), 0)
|
|
99
99
|
self.assertEqual(wrapper(0), 0)
|
|
100
100
|
self.assertEqual(lock.count, 3)
|
|
101
|
-
self.assertEqual(cond.wait_count,
|
|
101
|
+
self.assertEqual(cond.wait_count, 0)
|
|
102
102
|
self.assertEqual(cond.notify_count, 1)
|
|
103
103
|
self.assertEqual(wrapper(1), 1)
|
|
104
104
|
self.assertEqual(lock.count, 6)
|
|
105
|
-
self.assertEqual(cond.wait_count,
|
|
105
|
+
self.assertEqual(cond.wait_count, 0)
|
|
106
106
|
self.assertEqual(cond.notify_count, 2)
|
|
107
107
|
self.assertEqual(wrapper(1), 1)
|
|
108
108
|
self.assertEqual(lock.count, 7)
|
|
109
|
-
self.assertEqual(cond.wait_count,
|
|
109
|
+
self.assertEqual(cond.wait_count, 0)
|
|
110
110
|
self.assertEqual(cond.notify_count, 2)
|
|
111
111
|
|
|
112
112
|
def test_decorator_lock_cond(self):
|
|
@@ -118,15 +118,15 @@ class DecoratorTestMixin(SimplifiedTestCaseProtocol):
|
|
|
118
118
|
self.assertEqual(len(cache), 0)
|
|
119
119
|
self.assertEqual(wrapper(0), 0)
|
|
120
120
|
self.assertEqual(lock.count, 3)
|
|
121
|
-
self.assertEqual(cond.wait_count,
|
|
121
|
+
self.assertEqual(cond.wait_count, 0)
|
|
122
122
|
self.assertEqual(cond.notify_count, 1)
|
|
123
123
|
self.assertEqual(wrapper(1), 1)
|
|
124
124
|
self.assertEqual(lock.count, 6)
|
|
125
|
-
self.assertEqual(cond.wait_count,
|
|
125
|
+
self.assertEqual(cond.wait_count, 0)
|
|
126
126
|
self.assertEqual(cond.notify_count, 2)
|
|
127
127
|
self.assertEqual(wrapper(1), 1)
|
|
128
128
|
self.assertEqual(lock.count, 7)
|
|
129
|
-
self.assertEqual(cond.wait_count,
|
|
129
|
+
self.assertEqual(cond.wait_count, 0)
|
|
130
130
|
self.assertEqual(cond.notify_count, 2)
|
|
131
131
|
|
|
132
132
|
def test_decorator_cond_error(self):
|
|
@@ -138,7 +138,7 @@ class DecoratorTestMixin(SimplifiedTestCaseProtocol):
|
|
|
138
138
|
wrapper(0)
|
|
139
139
|
self.assertEqual(len(cache), 0)
|
|
140
140
|
self.assertEqual(lock.count, 2)
|
|
141
|
-
self.assertEqual(cond.wait_count,
|
|
141
|
+
self.assertEqual(cond.wait_count, 0)
|
|
142
142
|
self.assertEqual(cond.notify_count, 1)
|
|
143
143
|
|
|
144
144
|
# verify pending set is cleaned up, otherwise this might deadlock
|
|
@@ -146,7 +146,7 @@ class DecoratorTestMixin(SimplifiedTestCaseProtocol):
|
|
|
146
146
|
wrapper(0)
|
|
147
147
|
self.assertEqual(len(cache), 0)
|
|
148
148
|
self.assertEqual(lock.count, 4)
|
|
149
|
-
self.assertEqual(cond.wait_count,
|
|
149
|
+
self.assertEqual(cond.wait_count, 0)
|
|
150
150
|
self.assertEqual(cond.notify_count, 2)
|
|
151
151
|
|
|
152
152
|
def test_decorator_wrapped(self):
|
|
@@ -150,29 +150,29 @@ class MethodDecoratorTestMixin(SimplifiedTestCaseProtocol):
|
|
|
150
150
|
|
|
151
151
|
self.assertEqual(cached.get_cond(0), 0)
|
|
152
152
|
self.assertEqual(cached.cond.count, 3)
|
|
153
|
-
self.assertEqual(cached.cond.wait_count,
|
|
153
|
+
self.assertEqual(cached.cond.wait_count, 0)
|
|
154
154
|
self.assertEqual(cached.cond.notify_count, 1)
|
|
155
155
|
self.assertEqual(cached.get_cond(1), 1)
|
|
156
156
|
self.assertEqual(cached.cond.count, 6)
|
|
157
|
-
self.assertEqual(cached.cond.wait_count,
|
|
157
|
+
self.assertEqual(cached.cond.wait_count, 0)
|
|
158
158
|
self.assertEqual(cached.cond.notify_count, 2)
|
|
159
159
|
self.assertEqual(cached.get_cond(1), 1)
|
|
160
160
|
self.assertEqual(cached.cond.count, 7)
|
|
161
|
-
self.assertEqual(cached.cond.wait_count,
|
|
161
|
+
self.assertEqual(cached.cond.wait_count, 0)
|
|
162
162
|
self.assertEqual(cached.cond.notify_count, 2)
|
|
163
163
|
self.assertEqual(cached.get_cond(1.0), 1)
|
|
164
164
|
self.assertEqual(cached.cond.count, 8)
|
|
165
|
-
self.assertEqual(cached.cond.wait_count,
|
|
165
|
+
self.assertEqual(cached.cond.wait_count, 0)
|
|
166
166
|
self.assertEqual(cached.cond.notify_count, 2)
|
|
167
167
|
self.assertEqual(cached.get_cond(1.0), 1)
|
|
168
168
|
self.assertEqual(cached.cond.count, 9)
|
|
169
|
-
self.assertEqual(cached.cond.wait_count,
|
|
169
|
+
self.assertEqual(cached.cond.wait_count, 0)
|
|
170
170
|
self.assertEqual(cached.cond.notify_count, 2)
|
|
171
171
|
|
|
172
172
|
cached.cache.clear()
|
|
173
173
|
self.assertEqual(cached.get_cond(1), 2)
|
|
174
174
|
self.assertEqual(cached.cond.count, 12)
|
|
175
|
-
self.assertEqual(cached.cond.wait_count,
|
|
175
|
+
self.assertEqual(cached.cond.wait_count, 0)
|
|
176
176
|
self.assertEqual(cached.cond.notify_count, 3)
|
|
177
177
|
|
|
178
178
|
def test_decorator_info(self):
|
|
@@ -255,19 +255,19 @@ class MethodDecoratorTestMixin(SimplifiedTestCaseProtocol):
|
|
|
255
255
|
self.assertEqual(cached.cond.count, 1)
|
|
256
256
|
self.assertEqual(wrapper(0), 0)
|
|
257
257
|
self.assertEqual(cached.cond.count, 4)
|
|
258
|
-
self.assertEqual(cached.cond.wait_count,
|
|
258
|
+
self.assertEqual(cached.cond.wait_count, 0)
|
|
259
259
|
self.assertEqual(cached.cond.notify_count, 1)
|
|
260
260
|
self.assertEqual(wrapper.cache_info(), (0, 1, maxsize, 1))
|
|
261
261
|
self.assertEqual(cached.cond.count, 5)
|
|
262
262
|
self.assertEqual(wrapper(1), 1)
|
|
263
263
|
self.assertEqual(cached.cond.count, 8)
|
|
264
|
-
self.assertEqual(cached.cond.wait_count,
|
|
264
|
+
self.assertEqual(cached.cond.wait_count, 0)
|
|
265
265
|
self.assertEqual(cached.cond.notify_count, 2)
|
|
266
266
|
self.assertEqual(wrapper.cache_info(), (0, 2, maxsize, 2))
|
|
267
267
|
self.assertEqual(cached.cond.count, 9)
|
|
268
268
|
self.assertEqual(wrapper(0), 0)
|
|
269
269
|
self.assertEqual(cached.cond.count, 10)
|
|
270
|
-
self.assertEqual(cached.cond.wait_count,
|
|
270
|
+
self.assertEqual(cached.cond.wait_count, 0)
|
|
271
271
|
self.assertEqual(cached.cond.notify_count, 2)
|
|
272
272
|
self.assertEqual(wrapper.cache_info(), (1, 2, maxsize, 2))
|
|
273
273
|
self.assertEqual(cached.cond.count, 11)
|
|
@@ -279,13 +279,13 @@ class MethodDecoratorTestMixin(SimplifiedTestCaseProtocol):
|
|
|
279
279
|
self.assertEqual(cached.cond.count, 13)
|
|
280
280
|
self.assertEqual(wrapper(0), 2)
|
|
281
281
|
self.assertEqual(cached.cond.count, 16)
|
|
282
|
-
self.assertEqual(cached.cond.wait_count,
|
|
282
|
+
self.assertEqual(cached.cond.wait_count, 0)
|
|
283
283
|
self.assertEqual(cached.cond.notify_count, 3)
|
|
284
284
|
self.assertEqual(wrapper.cache_info(), (0, 1, maxsize, 1))
|
|
285
285
|
self.assertEqual(cached.cond.count, 17)
|
|
286
286
|
self.assertEqual(wrapper(0), 2)
|
|
287
287
|
self.assertEqual(cached.cond.count, 18)
|
|
288
|
-
self.assertEqual(cached.cond.wait_count,
|
|
288
|
+
self.assertEqual(cached.cond.wait_count, 0)
|
|
289
289
|
self.assertEqual(cached.cond.notify_count, 3)
|
|
290
290
|
self.assertEqual(wrapper.cache_info(), (1, 1, maxsize, 1))
|
|
291
291
|
self.assertEqual(cached.cond.count, 19)
|
|
@@ -306,19 +306,19 @@ class MethodDecoratorTestMixin(SimplifiedTestCaseProtocol):
|
|
|
306
306
|
self.assertEqual(cached.lock.count, 1)
|
|
307
307
|
self.assertEqual(wrapper(0), 0)
|
|
308
308
|
self.assertEqual(cached.lock.count, 4)
|
|
309
|
-
self.assertEqual(cached.cond.wait_count,
|
|
309
|
+
self.assertEqual(cached.cond.wait_count, 0)
|
|
310
310
|
self.assertEqual(cached.cond.notify_count, 1)
|
|
311
311
|
self.assertEqual(wrapper.cache_info(), (0, 1, maxsize, 1))
|
|
312
312
|
self.assertEqual(cached.lock.count, 5)
|
|
313
313
|
self.assertEqual(wrapper(1), 1)
|
|
314
314
|
self.assertEqual(cached.lock.count, 8)
|
|
315
|
-
self.assertEqual(cached.cond.wait_count,
|
|
315
|
+
self.assertEqual(cached.cond.wait_count, 0)
|
|
316
316
|
self.assertEqual(cached.cond.notify_count, 2)
|
|
317
317
|
self.assertEqual(wrapper.cache_info(), (0, 2, maxsize, 2))
|
|
318
318
|
self.assertEqual(cached.lock.count, 9)
|
|
319
319
|
self.assertEqual(wrapper(0), 0)
|
|
320
320
|
self.assertEqual(cached.lock.count, 10)
|
|
321
|
-
self.assertEqual(cached.cond.wait_count,
|
|
321
|
+
self.assertEqual(cached.cond.wait_count, 0)
|
|
322
322
|
self.assertEqual(cached.cond.notify_count, 2)
|
|
323
323
|
self.assertEqual(wrapper.cache_info(), (1, 2, maxsize, 2))
|
|
324
324
|
self.assertEqual(cached.lock.count, 11)
|
|
@@ -330,13 +330,13 @@ class MethodDecoratorTestMixin(SimplifiedTestCaseProtocol):
|
|
|
330
330
|
self.assertEqual(cached.lock.count, 13)
|
|
331
331
|
self.assertEqual(wrapper(0), 2)
|
|
332
332
|
self.assertEqual(cached.lock.count, 16)
|
|
333
|
-
self.assertEqual(cached.cond.wait_count,
|
|
333
|
+
self.assertEqual(cached.cond.wait_count, 0)
|
|
334
334
|
self.assertEqual(cached.cond.notify_count, 3)
|
|
335
335
|
self.assertEqual(wrapper.cache_info(), (0, 1, maxsize, 1))
|
|
336
336
|
self.assertEqual(cached.lock.count, 17)
|
|
337
337
|
self.assertEqual(wrapper(0), 2)
|
|
338
338
|
self.assertEqual(cached.lock.count, 18)
|
|
339
|
-
self.assertEqual(cached.cond.wait_count,
|
|
339
|
+
self.assertEqual(cached.cond.wait_count, 0)
|
|
340
340
|
self.assertEqual(cached.cond.notify_count, 3)
|
|
341
341
|
self.assertEqual(wrapper.cache_info(), (1, 1, maxsize, 1))
|
|
342
342
|
self.assertEqual(cached.lock.count, 19)
|
|
@@ -352,14 +352,14 @@ class MethodDecoratorTestMixin(SimplifiedTestCaseProtocol):
|
|
|
352
352
|
with self.assertRaises(ValueError):
|
|
353
353
|
cached.get_cond_error(0)
|
|
354
354
|
self.assertEqual(cached.cond.count, 2)
|
|
355
|
-
self.assertEqual(cached.cond.wait_count,
|
|
355
|
+
self.assertEqual(cached.cond.wait_count, 0)
|
|
356
356
|
self.assertEqual(cached.cond.notify_count, 1)
|
|
357
357
|
|
|
358
358
|
# verify pending set is cleaned up, otherwise this might deadlock
|
|
359
359
|
with self.assertRaises(ValueError):
|
|
360
360
|
cached.get_cond_error(0)
|
|
361
361
|
self.assertEqual(cached.cond.count, 4)
|
|
362
|
-
self.assertEqual(cached.cond.wait_count,
|
|
362
|
+
self.assertEqual(cached.cond.wait_count, 0)
|
|
363
363
|
self.assertEqual(cached.cond.notify_count, 2)
|
|
364
364
|
|
|
365
365
|
def test_decorator_wrapped(self):
|
|
@@ -701,36 +701,36 @@ class CacheMethodTest(unittest.TestCase, MethodDecoratorTestMixin):
|
|
|
701
701
|
|
|
702
702
|
self.assertEqual(cached.get_cond(0), 0)
|
|
703
703
|
self.assertEqual(cached.cond.count, 3)
|
|
704
|
-
self.assertEqual(cached.cond.wait_count,
|
|
704
|
+
self.assertEqual(cached.cond.wait_count, 0)
|
|
705
705
|
self.assertEqual(cached.cond.notify_count, 1)
|
|
706
706
|
self.assertEqual(cached.get_cond(1), 1)
|
|
707
707
|
self.assertEqual(cached.cond.count, 6)
|
|
708
|
-
self.assertEqual(cached.cond.wait_count,
|
|
708
|
+
self.assertEqual(cached.cond.wait_count, 0)
|
|
709
709
|
self.assertEqual(cached.cond.notify_count, 2)
|
|
710
710
|
self.assertEqual(cached.get_cond(1), 2)
|
|
711
711
|
self.assertEqual(cached.cond.count, 9)
|
|
712
|
-
self.assertEqual(cached.cond.wait_count,
|
|
712
|
+
self.assertEqual(cached.cond.wait_count, 0)
|
|
713
713
|
self.assertEqual(cached.cond.notify_count, 3)
|
|
714
714
|
self.assertEqual(cached.get_cond(1.0), 3)
|
|
715
715
|
self.assertEqual(cached.cond.count, 12)
|
|
716
|
-
self.assertEqual(cached.cond.wait_count,
|
|
716
|
+
self.assertEqual(cached.cond.wait_count, 0)
|
|
717
717
|
self.assertEqual(cached.cond.notify_count, 4)
|
|
718
718
|
self.assertEqual(cached.get_cond(1.0), 4)
|
|
719
719
|
self.assertEqual(cached.cond.count, 15)
|
|
720
|
-
self.assertEqual(cached.cond.wait_count,
|
|
720
|
+
self.assertEqual(cached.cond.wait_count, 0)
|
|
721
721
|
self.assertEqual(cached.cond.notify_count, 5)
|
|
722
722
|
|
|
723
723
|
self.assertEqual(cached.get_cond_info(0), 5)
|
|
724
724
|
self.assertEqual(cached.cond.count, 18)
|
|
725
|
-
self.assertEqual(cached.cond.wait_count,
|
|
725
|
+
self.assertEqual(cached.cond.wait_count, 0)
|
|
726
726
|
self.assertEqual(cached.cond.notify_count, 6)
|
|
727
727
|
self.assertEqual(cached.get_cond_info(1), 6)
|
|
728
728
|
self.assertEqual(cached.cond.count, 21)
|
|
729
|
-
self.assertEqual(cached.cond.wait_count,
|
|
729
|
+
self.assertEqual(cached.cond.wait_count, 0)
|
|
730
730
|
self.assertEqual(cached.cond.notify_count, 7)
|
|
731
731
|
self.assertEqual(cached.get_cond(1.0), 7)
|
|
732
732
|
self.assertEqual(cached.cond.count, 24)
|
|
733
|
-
self.assertEqual(cached.cond.wait_count,
|
|
733
|
+
self.assertEqual(cached.cond.wait_count, 0)
|
|
734
734
|
self.assertEqual(cached.cond.notify_count, 8)
|
|
735
735
|
|
|
736
736
|
def test_shared_cache(self):
|
|
@@ -82,10 +82,9 @@ class CacheKeysTest(unittest.TestCase):
|
|
|
82
82
|
import pickle
|
|
83
83
|
|
|
84
84
|
for k in [key(), key("abc"), key("abc", 123), key("abc", q="abc")]:
|
|
85
|
-
# white-box test: assert cached hash value is not pickled
|
|
86
85
|
self.assertEqual(len(k.__dict__), 0)
|
|
87
86
|
h = hash(k)
|
|
88
|
-
self.assertEqual(
|
|
87
|
+
self.assertEqual(h, hash(tuple(k)))
|
|
89
88
|
pickled = pickle.loads(pickle.dumps(k))
|
|
90
89
|
self.assertEqual(len(pickled.__dict__), 0)
|
|
91
90
|
self.assertEqual(k, pickled)
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
import unittest
|
|
2
|
-
from threading import Barrier, Condition, Lock, Thread
|
|
2
|
+
from threading import Barrier, Condition, Event, Lock, Thread
|
|
3
3
|
|
|
4
4
|
from cachetools import LRUCache, cached, cachedmethod
|
|
5
5
|
|
|
6
6
|
|
|
7
|
+
class WaitCondition(Condition):
|
|
8
|
+
def __init__(self):
|
|
9
|
+
super().__init__()
|
|
10
|
+
self.waiting = Event()
|
|
11
|
+
|
|
12
|
+
def wait(self, timeout=None):
|
|
13
|
+
self.waiting.set()
|
|
14
|
+
return super().wait(timeout)
|
|
15
|
+
|
|
16
|
+
|
|
7
17
|
class ThreadingTest(unittest.TestCase):
|
|
8
18
|
NTHREADS = 10
|
|
9
19
|
|
|
@@ -43,27 +53,66 @@ class ThreadingTest(unittest.TestCase):
|
|
|
43
53
|
for info in (False, True):
|
|
44
54
|
with self.subTest(info=info):
|
|
45
55
|
cache = LRUCache(1)
|
|
56
|
+
cond = WaitCondition()
|
|
57
|
+
started = Event()
|
|
58
|
+
release = Event()
|
|
59
|
+
calls = []
|
|
46
60
|
|
|
47
|
-
@cached(cache, condition=
|
|
48
|
-
def func():
|
|
49
|
-
|
|
50
|
-
|
|
61
|
+
@cached(cache, condition=cond, info=info)
|
|
62
|
+
def func(calls=calls, started=started, release=release):
|
|
63
|
+
calls.append(None)
|
|
64
|
+
started.set()
|
|
65
|
+
release.wait(self.TIMEOUT)
|
|
66
|
+
return object()
|
|
51
67
|
|
|
52
|
-
self.
|
|
68
|
+
self.run_cond_wait_test(func, cond, started, release, calls)
|
|
53
69
|
|
|
54
70
|
def test_cachedmethod_cond(self):
|
|
55
71
|
for info in (False, True):
|
|
56
72
|
with self.subTest(info=info):
|
|
57
73
|
cache = LRUCache(1)
|
|
58
|
-
cond =
|
|
74
|
+
cond = WaitCondition()
|
|
75
|
+
started = Event()
|
|
76
|
+
release = Event()
|
|
77
|
+
calls = []
|
|
59
78
|
|
|
60
79
|
class Cached:
|
|
61
80
|
@cachedmethod(lambda _: cache, condition=lambda _: cond, info=info)
|
|
62
|
-
def method(
|
|
63
|
-
|
|
64
|
-
|
|
81
|
+
def method(
|
|
82
|
+
self,
|
|
83
|
+
calls=calls,
|
|
84
|
+
started=started,
|
|
85
|
+
release=release,
|
|
86
|
+
timeout=self.TIMEOUT,
|
|
87
|
+
):
|
|
88
|
+
calls.append(None)
|
|
89
|
+
started.set()
|
|
90
|
+
release.wait(timeout)
|
|
91
|
+
return object()
|
|
92
|
+
|
|
93
|
+
func = Cached().method
|
|
94
|
+
self.run_cond_wait_test(func, cond, started, release, calls)
|
|
95
|
+
|
|
96
|
+
def run_cond_wait_test(self, func, cond, started, release, calls):
|
|
97
|
+
results = [None] * 2
|
|
98
|
+
|
|
99
|
+
def run(index):
|
|
100
|
+
results[index] = func()
|
|
65
101
|
|
|
66
|
-
|
|
102
|
+
first = Thread(target=run, args=[0])
|
|
103
|
+
first.start()
|
|
104
|
+
self.assertTrue(started.wait(self.TIMEOUT))
|
|
105
|
+
second = Thread(target=run, args=[1])
|
|
106
|
+
second.start()
|
|
107
|
+
# second caller must block on the pending key
|
|
108
|
+
self.assertTrue(cond.waiting.wait(self.TIMEOUT))
|
|
109
|
+
release.set()
|
|
110
|
+
for thread in (first, second):
|
|
111
|
+
thread.join(timeout=self.TIMEOUT)
|
|
112
|
+
self.assertFalse(thread.is_alive())
|
|
113
|
+
self.assertEqual(len(calls), 1)
|
|
114
|
+
self.assertIsNotNone(results[0])
|
|
115
|
+
self.assertIs(results[0], results[1])
|
|
67
116
|
|
|
68
117
|
def run_lock_test(self, func, cache, info):
|
|
69
118
|
self.run_threading_test(func, 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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|