cachetools 7.2.3__tar.gz → 7.2.4__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.3 → cachetools-7.2.4}/CHANGELOG.rst +10 -0
- {cachetools-7.2.3/src/cachetools.egg-info → cachetools-7.2.4}/PKG-INFO +1 -1
- {cachetools-7.2.3 → cachetools-7.2.4}/src/cachetools/__init__.py +1 -1
- {cachetools-7.2.3 → cachetools-7.2.4}/src/cachetools/__init__.pyi +2 -1
- {cachetools-7.2.3 → cachetools-7.2.4}/src/cachetools/_descriptor.py +22 -7
- {cachetools-7.2.3 → cachetools-7.2.4/src/cachetools.egg-info}/PKG-INFO +1 -1
- {cachetools-7.2.3 → cachetools-7.2.4}/tests/__init__.py +40 -36
- {cachetools-7.2.3 → cachetools-7.2.4}/tests/test_cachedmethod.py +25 -0
- {cachetools-7.2.3 → cachetools-7.2.4}/tests/test_tlru.py +13 -0
- {cachetools-7.2.3 → cachetools-7.2.4}/tests/test_ttl.py +11 -0
- {cachetools-7.2.3 → cachetools-7.2.4}/tox.ini +1 -1
- {cachetools-7.2.3 → cachetools-7.2.4}/LICENSE +0 -0
- {cachetools-7.2.3 → cachetools-7.2.4}/MANIFEST.in +0 -0
- {cachetools-7.2.3 → cachetools-7.2.4}/README.rst +0 -0
- {cachetools-7.2.3 → cachetools-7.2.4}/docs/conf.py +0 -0
- {cachetools-7.2.3 → cachetools-7.2.4}/docs/index.rst +0 -0
- {cachetools-7.2.3 → cachetools-7.2.4}/pyproject.toml +0 -0
- {cachetools-7.2.3 → cachetools-7.2.4}/setup.cfg +0 -0
- {cachetools-7.2.3 → cachetools-7.2.4}/src/cachetools/_cached.py +0 -0
- {cachetools-7.2.3 → cachetools-7.2.4}/src/cachetools/_cachedmethod.py +0 -0
- {cachetools-7.2.3 → cachetools-7.2.4}/src/cachetools/func.py +0 -0
- {cachetools-7.2.3 → cachetools-7.2.4}/src/cachetools/func.pyi +0 -0
- {cachetools-7.2.3 → cachetools-7.2.4}/src/cachetools/keys.py +0 -0
- {cachetools-7.2.3 → cachetools-7.2.4}/src/cachetools/keys.pyi +0 -0
- {cachetools-7.2.3 → cachetools-7.2.4}/src/cachetools/py.typed +0 -0
- {cachetools-7.2.3 → cachetools-7.2.4}/src/cachetools.egg-info/SOURCES.txt +0 -0
- {cachetools-7.2.3 → cachetools-7.2.4}/src/cachetools.egg-info/dependency_links.txt +0 -0
- {cachetools-7.2.3 → cachetools-7.2.4}/src/cachetools.egg-info/top_level.txt +0 -0
- {cachetools-7.2.3 → cachetools-7.2.4}/tests/test_cache.py +0 -0
- {cachetools-7.2.3 → cachetools-7.2.4}/tests/test_cached.py +0 -0
- {cachetools-7.2.3 → cachetools-7.2.4}/tests/test_classmethod.py +0 -0
- {cachetools-7.2.3 → cachetools-7.2.4}/tests/test_fifo.py +0 -0
- {cachetools-7.2.3 → cachetools-7.2.4}/tests/test_func.py +0 -0
- {cachetools-7.2.3 → cachetools-7.2.4}/tests/test_keys.py +0 -0
- {cachetools-7.2.3 → cachetools-7.2.4}/tests/test_lfu.py +0 -0
- {cachetools-7.2.3 → cachetools-7.2.4}/tests/test_lru.py +0 -0
- {cachetools-7.2.3 → cachetools-7.2.4}/tests/test_rr.py +0 -0
- {cachetools-7.2.3 → cachetools-7.2.4}/tests/test_threading.py +0 -0
|
@@ -143,7 +143,8 @@ class _CacheInfo(NamedTuple):
|
|
|
143
143
|
|
|
144
144
|
@type_check_only
|
|
145
145
|
class _AbstractCondition(AbstractContextManager[Any], Protocol):
|
|
146
|
-
# implementation and unit tests do not use plain wait()
|
|
146
|
+
# implementation and unit tests currently do not use plain wait()
|
|
147
|
+
# and notify(); these are reserved for future use
|
|
147
148
|
def wait(self, timeout: float | None = None) -> bool: ...
|
|
148
149
|
def wait_for(
|
|
149
150
|
self, predicate: Callable[[], _T], timeout: float | None = None
|
|
@@ -96,15 +96,30 @@ class _MethodDescriptor:
|
|
|
96
96
|
# Created once and shared by all class accesses; objtype is not
|
|
97
97
|
# used for instance lookups, so it need not be bound here.
|
|
98
98
|
if self.__unbound is None:
|
|
99
|
-
|
|
99
|
+
self.__unbound = _Unbound(self)
|
|
100
|
+
return self.__unbound
|
|
100
101
|
|
|
101
|
-
# As in the previous function wrapper, the receiver is named self.
|
|
102
|
-
@functools.wraps(self.__wrapped__) # type: ignore
|
|
103
|
-
def unbound(self, *args, **kwargs):
|
|
104
|
-
return descriptor.__get__(self)(*args, **kwargs)
|
|
105
102
|
|
|
106
|
-
|
|
107
|
-
|
|
103
|
+
class _Unbound:
|
|
104
|
+
"""Callable returned on class access.
|
|
105
|
+
|
|
106
|
+
Deliberately not a function: unittest.mock.create_autospec() only strips
|
|
107
|
+
the receiver from plain functions found in the class dict, so a function
|
|
108
|
+
here would leave the receiver in an autospec'd instance's signature.
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
def __init__(self, descriptor):
|
|
112
|
+
functools.update_wrapper(self, descriptor.__wrapped__)
|
|
113
|
+
self.__descriptor = descriptor
|
|
114
|
+
|
|
115
|
+
# Receiver is taken from *args so autospec'd instances accept any call,
|
|
116
|
+
# as they did before class access returned a callable.
|
|
117
|
+
def __call__(self, /, *args, **kwargs):
|
|
118
|
+
if not args:
|
|
119
|
+
msg = "Cannot call @cachedmethod through the class without an instance"
|
|
120
|
+
raise TypeError(msg)
|
|
121
|
+
obj, *args = args
|
|
122
|
+
return self.__descriptor.__get__(obj)(*args, **kwargs)
|
|
108
123
|
|
|
109
124
|
|
|
110
125
|
class _ClassMethodDescriptor(_MethodDescriptor):
|
|
@@ -5,6 +5,39 @@ from typing import Any, Protocol
|
|
|
5
5
|
import cachetools
|
|
6
6
|
|
|
7
7
|
|
|
8
|
+
class CountedLock:
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self.count = 0
|
|
11
|
+
|
|
12
|
+
def __enter__(self):
|
|
13
|
+
self.count += 1
|
|
14
|
+
|
|
15
|
+
def __exit__(self, *exc):
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CountedCondition(CountedLock):
|
|
20
|
+
def __init__(self):
|
|
21
|
+
super().__init__()
|
|
22
|
+
self.wait_count = 0
|
|
23
|
+
self.notify_count = 0
|
|
24
|
+
|
|
25
|
+
def wait(self, timeout=None):
|
|
26
|
+
self.wait_count += 1
|
|
27
|
+
return True
|
|
28
|
+
|
|
29
|
+
def wait_for(self, predicate, timeout=None):
|
|
30
|
+
res = predicate()
|
|
31
|
+
self.wait_count += 1
|
|
32
|
+
return res
|
|
33
|
+
|
|
34
|
+
def notify(self, n=1):
|
|
35
|
+
self.notify_count += 1
|
|
36
|
+
|
|
37
|
+
def notify_all(self):
|
|
38
|
+
self.notify_count += 1
|
|
39
|
+
|
|
40
|
+
|
|
8
41
|
class SimplifiedTestCaseProtocol(Protocol):
|
|
9
42
|
def assertEqual(self, first: Any, second: Any, msg: Any = None) -> None: ...
|
|
10
43
|
def assertNotEqual(self, first: Any, second: Any, msg: Any = None) -> None: ...
|
|
@@ -323,6 +356,13 @@ class CacheTestMixin(SimplifiedTestCaseProtocol):
|
|
|
323
356
|
self.assertEqual(3, cache[3])
|
|
324
357
|
self.assertEqual(7, cache[4])
|
|
325
358
|
|
|
359
|
+
# growing an existing entry may have to evict other entries
|
|
360
|
+
cache[4] = 9
|
|
361
|
+
self.assertEqual(1, len(cache))
|
|
362
|
+
self.assertEqual(9, cache.currsize)
|
|
363
|
+
self.assertNotIn(3, cache)
|
|
364
|
+
self.assertEqual(9, cache[4])
|
|
365
|
+
|
|
326
366
|
def test_getsizeof_replace_same_size(self):
|
|
327
367
|
cache = self.Cache(maxsize=10, getsizeof=lambda x: x)
|
|
328
368
|
|
|
@@ -453,39 +493,3 @@ class CacheTestMixin(SimplifiedTestCaseProtocol):
|
|
|
453
493
|
cache = pickle.loads(pickle.dumps(source))
|
|
454
494
|
self.assertEqual(n, len(cache))
|
|
455
495
|
self.assertEqual(source, cache)
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
class CountedLock:
|
|
459
|
-
def __init__(self):
|
|
460
|
-
self.count = 0
|
|
461
|
-
|
|
462
|
-
def __enter__(self):
|
|
463
|
-
self.count += 1
|
|
464
|
-
|
|
465
|
-
def __exit__(self, *exc):
|
|
466
|
-
pass
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
class CountedCondition(CountedLock):
|
|
470
|
-
def __init__(self):
|
|
471
|
-
super().__init__()
|
|
472
|
-
self.wait_count = 0
|
|
473
|
-
self.notify_count = 0
|
|
474
|
-
|
|
475
|
-
# only wait_for() and notify_all() are currently used, but wait()
|
|
476
|
-
# and notify() are also required according to the docs...
|
|
477
|
-
|
|
478
|
-
def wait(self, timeout=None):
|
|
479
|
-
self.wait_count += 1
|
|
480
|
-
return True
|
|
481
|
-
|
|
482
|
-
def wait_for(self, predicate, timeout=None):
|
|
483
|
-
res = predicate()
|
|
484
|
-
self.wait_count += 1
|
|
485
|
-
return res
|
|
486
|
-
|
|
487
|
-
def notify(self, n=1):
|
|
488
|
-
self.notify_count += 1
|
|
489
|
-
|
|
490
|
-
def notify_all(self):
|
|
491
|
-
self.notify_count += 1
|
|
@@ -500,6 +500,17 @@ class MethodDecoratorTestMixin(SimplifiedTestCaseProtocol):
|
|
|
500
500
|
|
|
501
501
|
bar = foo
|
|
502
502
|
|
|
503
|
+
def test_decorator_same_name(self):
|
|
504
|
+
# assigning to a second owner under the same name is accepted
|
|
505
|
+
class Other(Cached):
|
|
506
|
+
get = Cached.__dict__["get"]
|
|
507
|
+
|
|
508
|
+
other = Other(self.cache(2))
|
|
509
|
+
|
|
510
|
+
self.assertEqual(other.get(0), 0)
|
|
511
|
+
self.assertEqual(other.get(0), 0)
|
|
512
|
+
self.assertEqual(other.get(1), 1)
|
|
513
|
+
|
|
503
514
|
def test_decorator_no_set_name(self):
|
|
504
515
|
class Cached:
|
|
505
516
|
def foo(_self):
|
|
@@ -776,6 +787,10 @@ class UnboundMethodTest(unittest.TestCase):
|
|
|
776
787
|
self.assertEqual(cached.get(1), 0)
|
|
777
788
|
self.assertEqual(cached.count, 1)
|
|
778
789
|
|
|
790
|
+
def test_class_call_without_instance(self):
|
|
791
|
+
with self.assertRaises(TypeError):
|
|
792
|
+
Cached.get() # type: ignore
|
|
793
|
+
|
|
779
794
|
def test_class_access_identity(self):
|
|
780
795
|
self.assertIs(Cached.get, Cached.get)
|
|
781
796
|
self.assertIs(Cached.get_info, Cached.get_info)
|
|
@@ -798,3 +813,13 @@ class UnboundMethodTest(unittest.TestCase):
|
|
|
798
813
|
warnings.simplefilter("always")
|
|
799
814
|
unittest.mock.create_autospec(Cached, instance=True)
|
|
800
815
|
self.assertEqual(len(w), 0)
|
|
816
|
+
|
|
817
|
+
def test_autospec_call(self):
|
|
818
|
+
import unittest.mock
|
|
819
|
+
|
|
820
|
+
mock = unittest.mock.create_autospec(Cached, instance=True)
|
|
821
|
+
mock.get.return_value = 42
|
|
822
|
+
self.assertEqual(mock.get(1), 42)
|
|
823
|
+
mock.get.assert_called_with(1)
|
|
824
|
+
self.assertEqual(mock.get(value=1), 42)
|
|
825
|
+
mock.get.assert_called_with(value=1)
|
|
@@ -217,6 +217,19 @@ class TLRUCacheTest(unittest.TestCase, CacheTestMixin):
|
|
|
217
217
|
self.assertNotIn(2, cache)
|
|
218
218
|
self.assertNotIn(3, cache)
|
|
219
219
|
|
|
220
|
+
def test_ttu_iter_expired(self):
|
|
221
|
+
cache = TLRUCache[int, int, int](
|
|
222
|
+
maxsize=2, ttu=lambda _k, _v, t: t + 2, timer=Timer()
|
|
223
|
+
)
|
|
224
|
+
cache[1] = 1
|
|
225
|
+
cache.timer.tick()
|
|
226
|
+
cache[2] = 2
|
|
227
|
+
cache.timer.tick()
|
|
228
|
+
# a bare for loop skips expired items without removing them
|
|
229
|
+
self.assertEqual([2], [key for key in cache])
|
|
230
|
+
self.assertEqual({(1, 1)}, set(cache.expire()))
|
|
231
|
+
self.assertEqual([2], [key for key in cache])
|
|
232
|
+
|
|
220
233
|
def test_ttu_expired(self):
|
|
221
234
|
cache = TLRUCache[int, None, int](
|
|
222
235
|
maxsize=1, ttu=lambda k, _, t: t + k, timer=Timer()
|
|
@@ -177,6 +177,17 @@ class TTLCacheTest(unittest.TestCase, CacheTestMixin):
|
|
|
177
177
|
self.assertNotIn(2, cache)
|
|
178
178
|
self.assertNotIn(3, cache)
|
|
179
179
|
|
|
180
|
+
def test_ttl_iter_expired(self):
|
|
181
|
+
cache = TTLCache[int, int, int](maxsize=2, ttl=2, timer=Timer())
|
|
182
|
+
cache[1] = 1
|
|
183
|
+
cache.timer.tick()
|
|
184
|
+
cache[2] = 2
|
|
185
|
+
cache.timer.tick()
|
|
186
|
+
# a bare for loop skips expired items without removing them
|
|
187
|
+
self.assertEqual([2], [key for key in cache])
|
|
188
|
+
self.assertEqual({(1, 1)}, set(cache.expire()))
|
|
189
|
+
self.assertEqual([2], [key for key in cache])
|
|
190
|
+
|
|
180
191
|
def test_ttl_atomic(self):
|
|
181
192
|
cache = TTLCache[int, int, int](maxsize=1, ttl=2, timer=Timer(auto=True))
|
|
182
193
|
cache[1] = 1
|
|
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
|