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.
Files changed (38) hide show
  1. {cachetools-7.2.3 → cachetools-7.2.4}/CHANGELOG.rst +10 -0
  2. {cachetools-7.2.3/src/cachetools.egg-info → cachetools-7.2.4}/PKG-INFO +1 -1
  3. {cachetools-7.2.3 → cachetools-7.2.4}/src/cachetools/__init__.py +1 -1
  4. {cachetools-7.2.3 → cachetools-7.2.4}/src/cachetools/__init__.pyi +2 -1
  5. {cachetools-7.2.3 → cachetools-7.2.4}/src/cachetools/_descriptor.py +22 -7
  6. {cachetools-7.2.3 → cachetools-7.2.4/src/cachetools.egg-info}/PKG-INFO +1 -1
  7. {cachetools-7.2.3 → cachetools-7.2.4}/tests/__init__.py +40 -36
  8. {cachetools-7.2.3 → cachetools-7.2.4}/tests/test_cachedmethod.py +25 -0
  9. {cachetools-7.2.3 → cachetools-7.2.4}/tests/test_tlru.py +13 -0
  10. {cachetools-7.2.3 → cachetools-7.2.4}/tests/test_ttl.py +11 -0
  11. {cachetools-7.2.3 → cachetools-7.2.4}/tox.ini +1 -1
  12. {cachetools-7.2.3 → cachetools-7.2.4}/LICENSE +0 -0
  13. {cachetools-7.2.3 → cachetools-7.2.4}/MANIFEST.in +0 -0
  14. {cachetools-7.2.3 → cachetools-7.2.4}/README.rst +0 -0
  15. {cachetools-7.2.3 → cachetools-7.2.4}/docs/conf.py +0 -0
  16. {cachetools-7.2.3 → cachetools-7.2.4}/docs/index.rst +0 -0
  17. {cachetools-7.2.3 → cachetools-7.2.4}/pyproject.toml +0 -0
  18. {cachetools-7.2.3 → cachetools-7.2.4}/setup.cfg +0 -0
  19. {cachetools-7.2.3 → cachetools-7.2.4}/src/cachetools/_cached.py +0 -0
  20. {cachetools-7.2.3 → cachetools-7.2.4}/src/cachetools/_cachedmethod.py +0 -0
  21. {cachetools-7.2.3 → cachetools-7.2.4}/src/cachetools/func.py +0 -0
  22. {cachetools-7.2.3 → cachetools-7.2.4}/src/cachetools/func.pyi +0 -0
  23. {cachetools-7.2.3 → cachetools-7.2.4}/src/cachetools/keys.py +0 -0
  24. {cachetools-7.2.3 → cachetools-7.2.4}/src/cachetools/keys.pyi +0 -0
  25. {cachetools-7.2.3 → cachetools-7.2.4}/src/cachetools/py.typed +0 -0
  26. {cachetools-7.2.3 → cachetools-7.2.4}/src/cachetools.egg-info/SOURCES.txt +0 -0
  27. {cachetools-7.2.3 → cachetools-7.2.4}/src/cachetools.egg-info/dependency_links.txt +0 -0
  28. {cachetools-7.2.3 → cachetools-7.2.4}/src/cachetools.egg-info/top_level.txt +0 -0
  29. {cachetools-7.2.3 → cachetools-7.2.4}/tests/test_cache.py +0 -0
  30. {cachetools-7.2.3 → cachetools-7.2.4}/tests/test_cached.py +0 -0
  31. {cachetools-7.2.3 → cachetools-7.2.4}/tests/test_classmethod.py +0 -0
  32. {cachetools-7.2.3 → cachetools-7.2.4}/tests/test_fifo.py +0 -0
  33. {cachetools-7.2.3 → cachetools-7.2.4}/tests/test_func.py +0 -0
  34. {cachetools-7.2.3 → cachetools-7.2.4}/tests/test_keys.py +0 -0
  35. {cachetools-7.2.3 → cachetools-7.2.4}/tests/test_lfu.py +0 -0
  36. {cachetools-7.2.3 → cachetools-7.2.4}/tests/test_lru.py +0 -0
  37. {cachetools-7.2.3 → cachetools-7.2.4}/tests/test_rr.py +0 -0
  38. {cachetools-7.2.3 → cachetools-7.2.4}/tests/test_threading.py +0 -0
@@ -1,3 +1,13 @@
1
+ v7.2.4 (2026-10-19)
2
+ ===================
3
+
4
+ - Restore autospec calls to ``@cachedmethod`` decorated methods.
5
+
6
+ - Improve branch coverage of unit tests.
7
+
8
+ - Minor type stub improvements.
9
+
10
+
1
11
  v7.2.3 (2026-10-17)
2
12
  ===================
3
13
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cachetools
3
- Version: 7.2.3
3
+ Version: 7.2.4
4
4
  Summary: Extensible memoizing collections and decorators
5
5
  Author-email: Thomas Kemmer <tkemmer@computer.org>
6
6
  Maintainer-email: Thomas Kemmer <tkemmer@computer.org>
@@ -12,7 +12,7 @@ __all__ = (
12
12
  "cachedmethod",
13
13
  )
14
14
 
15
- __version__ = "7.2.3"
15
+ __version__ = "7.2.4"
16
16
 
17
17
  import collections
18
18
  import collections.abc
@@ -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() and notify()
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
- descriptor = self
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
- self.__unbound = unbound # type: ignore
107
- return self.__unbound
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):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cachetools
3
- Version: 7.2.3
3
+ Version: 7.2.4
4
4
  Summary: Extensible memoizing collections and decorators
5
5
  Author-email: Thomas Kemmer <tkemmer@computer.org>
6
6
  Maintainer-email: Thomas Kemmer <tkemmer@computer.org>
@@ -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
@@ -6,7 +6,7 @@ deps =
6
6
  pytest
7
7
  pytest-cov
8
8
  commands =
9
- py.test --basetemp={envtmpdir} --cov=cachetools --cov-report term-missing {posargs}
9
+ py.test --basetemp={envtmpdir} --cov=cachetools --cov-branch --cov-report term-missing {posargs}
10
10
 
11
11
  [testenv:docs]
12
12
  deps =
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