cachetools 7.1.8__tar.gz → 7.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. {cachetools-7.1.8 → cachetools-7.2.1}/CHANGELOG.rst +24 -0
  2. {cachetools-7.1.8/src/cachetools.egg-info → cachetools-7.2.1}/PKG-INFO +2 -1
  3. {cachetools-7.1.8 → cachetools-7.2.1}/docs/index.rst +39 -29
  4. {cachetools-7.1.8 → cachetools-7.2.1}/pyproject.toml +1 -3
  5. {cachetools-7.1.8 → cachetools-7.2.1}/src/cachetools/__init__.py +6 -7
  6. {cachetools-7.1.8 → cachetools-7.2.1}/src/cachetools/_cached.py +10 -0
  7. {cachetools-7.1.8 → cachetools-7.2.1}/src/cachetools/_cachedmethod.py +2 -0
  8. {cachetools-7.1.8 → cachetools-7.2.1/src/cachetools.egg-info}/PKG-INFO +2 -1
  9. {cachetools-7.1.8 → cachetools-7.2.1}/tests/test_cached.py +57 -11
  10. {cachetools-7.1.8 → cachetools-7.2.1}/tests/test_lfu.py +21 -0
  11. {cachetools-7.1.8 → cachetools-7.2.1}/tests/test_rr.py +15 -0
  12. {cachetools-7.1.8 → cachetools-7.2.1}/LICENSE +0 -0
  13. {cachetools-7.1.8 → cachetools-7.2.1}/MANIFEST.in +0 -0
  14. {cachetools-7.1.8 → cachetools-7.2.1}/README.rst +0 -0
  15. {cachetools-7.1.8 → cachetools-7.2.1}/docs/conf.py +0 -0
  16. {cachetools-7.1.8 → cachetools-7.2.1}/setup.cfg +0 -0
  17. {cachetools-7.1.8 → cachetools-7.2.1}/src/cachetools/__init__.pyi +0 -0
  18. {cachetools-7.1.8 → cachetools-7.2.1}/src/cachetools/func.py +0 -0
  19. {cachetools-7.1.8 → cachetools-7.2.1}/src/cachetools/func.pyi +0 -0
  20. {cachetools-7.1.8 → cachetools-7.2.1}/src/cachetools/keys.py +0 -0
  21. {cachetools-7.1.8 → cachetools-7.2.1}/src/cachetools/keys.pyi +0 -0
  22. {cachetools-7.1.8 → cachetools-7.2.1}/src/cachetools/py.typed +0 -0
  23. {cachetools-7.1.8 → cachetools-7.2.1}/src/cachetools.egg-info/SOURCES.txt +0 -0
  24. {cachetools-7.1.8 → cachetools-7.2.1}/src/cachetools.egg-info/dependency_links.txt +0 -0
  25. {cachetools-7.1.8 → cachetools-7.2.1}/src/cachetools.egg-info/top_level.txt +0 -0
  26. {cachetools-7.1.8 → cachetools-7.2.1}/tests/__init__.py +0 -0
  27. {cachetools-7.1.8 → cachetools-7.2.1}/tests/test_cache.py +0 -0
  28. {cachetools-7.1.8 → cachetools-7.2.1}/tests/test_cachedmethod.py +0 -0
  29. {cachetools-7.1.8 → cachetools-7.2.1}/tests/test_classmethod.py +0 -0
  30. {cachetools-7.1.8 → cachetools-7.2.1}/tests/test_fifo.py +0 -0
  31. {cachetools-7.1.8 → cachetools-7.2.1}/tests/test_func.py +0 -0
  32. {cachetools-7.1.8 → cachetools-7.2.1}/tests/test_keys.py +0 -0
  33. {cachetools-7.1.8 → cachetools-7.2.1}/tests/test_lru.py +0 -0
  34. {cachetools-7.1.8 → cachetools-7.2.1}/tests/test_threading.py +0 -0
  35. {cachetools-7.1.8 → cachetools-7.2.1}/tests/test_tlru.py +0 -0
  36. {cachetools-7.1.8 → cachetools-7.2.1}/tests/test_ttl.py +0 -0
  37. {cachetools-7.1.8 → cachetools-7.2.1}/tox.ini +0 -0
@@ -1,3 +1,27 @@
1
+ v7.2.1 (2026-10-05)
2
+ ===================
3
+
4
+ - Improve error handling for ``RRCache.popitem()`` when the cache is
5
+ empty.
6
+
7
+ - Minor style and documentation improvements.
8
+
9
+ - Update CI environment.
10
+
11
+
12
+ v7.2.0 (2026-09-16)
13
+ ===================
14
+
15
+ - Deprecate use of ``cache=None`` to suppress caching with the
16
+ ``@cached`` decorator.
17
+
18
+ - Add support for Python 3.15.
19
+
20
+ - Minor test improvements.
21
+
22
+ - Minor documentation updates.
23
+
24
+
1
25
  v7.1.8 (2026-08-31)
2
26
  ===================
3
27
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cachetools
3
- Version: 7.1.8
3
+ Version: 7.2.1
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>
@@ -21,6 +21,7 @@ Classifier: Programming Language :: Python :: 3.11
21
21
  Classifier: Programming Language :: Python :: 3.12
22
22
  Classifier: Programming Language :: Python :: 3.13
23
23
  Classifier: Programming Language :: Python :: 3.14
24
+ Classifier: Programming Language :: Python :: 3.15
24
25
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
25
26
  Requires-Python: >=3.10
26
27
  Description-Content-Type: text/x-rst
@@ -261,7 +261,7 @@ key is not found:
261
261
  >>> class PepStore(LRUCache):
262
262
  ... def __missing__(self, key):
263
263
  ... """Retrieve text of a Python Enhancement Proposal"""
264
- ... url = 'http://www.python.org/dev/peps/pep-%04d/' % key
264
+ ... url = 'https://peps.python.org/pep-%04d/' % key
265
265
  ... with urllib.request.urlopen(url) as s:
266
266
  ... pep = s.read()
267
267
  ... self[key] = pep # store text in cache
@@ -364,7 +364,7 @@ often called with the same arguments:
364
364
  @cached(cache=LRUCache(maxsize=640*1024, getsizeof=len), lock=threading.Lock())
365
365
  def get_pep(num):
366
366
  'Retrieve text of a Python Enhancement Proposal'
367
- url = 'http://www.python.org/dev/peps/pep-%04d/' % num
367
+ url = 'https://peps.python.org/pep-%04d/' % num
368
368
  with urllib.request.urlopen(url) as s:
369
369
  return s.read()
370
370
 
@@ -396,7 +396,7 @@ often called with the same arguments:
396
396
 
397
397
  >>> @cached(cache=LRUCache(maxsize=32), info=True)
398
398
  ... def get_pep(num):
399
- ... url = 'http://www.python.org/dev/peps/pep-%04d/' % num
399
+ ... url = 'https://peps.python.org/pep-%04d/' % num
400
400
  ... with urllib.request.urlopen(url) as s:
401
401
  ... return s.read()
402
402
 
@@ -451,7 +451,7 @@ often called with the same arguments:
451
451
 
452
452
  @cached(cache=LRUCache(maxsize=10), info=True)
453
453
  def _get_pep_wrapped(num):
454
- url = "http://www.python.org/dev/peps/pep-%04d/" % num
454
+ url = "https://peps.python.org/pep-%04d/" % num
455
455
  try:
456
456
  with urllib.request.urlopen(url) as s:
457
457
  return s.read()
@@ -519,6 +519,13 @@ often called with the same arguments:
519
519
  >>> foo(1)
520
520
  >>> foo(a=1)
521
521
 
522
+ .. deprecated:: 7.2
523
+
524
+ Passing :const:`None` as `cache` parameter is deprecated, and
525
+ support will be removed in the next major version. This is
526
+ mainly for consistency with :func:`cachedmethod`, which no
527
+ longer supports returning :const:`None` from `cache(self)`.
528
+
522
529
 
523
530
  .. decorator:: cachedmethod(cache, key=cachetools.keys.methodkey, lock=None, condition=None, info=False)
524
531
 
@@ -566,7 +573,7 @@ often called with the same arguments:
566
573
  ... @cachedmethod(lambda self: self.cache, lock=lambda self: self.lock, info=True)
567
574
  ... def get(self, num):
568
575
  ... """Retrieve text of a Python Enhancement Proposal"""
569
- ... url = 'http://www.python.org/dev/peps/pep-%04d/' % num
576
+ ... url = 'https://peps.python.org/pep-%04d/' % num
570
577
  ... with urllib.request.urlopen(url) as s:
571
578
  ... return s.read()
572
579
 
@@ -620,7 +627,7 @@ often called with the same arguments:
620
627
  @cachedmethod(lambda self: self.cache, key=partial(methodkey, method='pep'))
621
628
  def get_pep(self, num):
622
629
  """Retrieve text of a Python Enhancement Proposal"""
623
- url = 'http://www.python.org/dev/peps/pep-%04d/' % num
630
+ url = 'https://peps.python.org/pep-%04d/' % num
624
631
  with urllib.request.urlopen(url) as s:
625
632
  return s.read()
626
633
 
@@ -649,31 +656,33 @@ often called with the same arguments:
649
656
 
650
657
  .. versionchanged:: 7.0
651
658
 
652
- Added the `info` option for reporting per-instance cache
653
- statistics.
659
+ Added the `info` option for reporting per-instance cache
660
+ statistics.
654
661
 
655
- :func:`cachedmethod` attributes (:attr:`cache`, :attr:`cache_lock`,
656
- etc.) are now implemented as properties for instance methods, and
657
- are finally officially supported.
662
+ :func:`cachedmethod` attributes (:attr:`cache`,
663
+ :attr:`cache_lock`, etc.) are now implemented as properties for
664
+ instance methods, and are finally officially supported.
658
665
 
659
- Require :attr:`__dict__` to be a mutable mapping to support
660
- per-instance :func:`cache_info()`.
666
+ Require :attr:`__dict__` to be a mutable mapping to support
667
+ per-instance :func:`cache_info()`.
661
668
 
662
- Returning :const:`None` from `cache(self)` is no longer supported.
669
+ Returning :const:`None` from `cache(self)` is no longer supported.
663
670
 
664
671
  .. deprecated:: 7.0
665
672
 
666
- Using :func:`cachedmethod` with :func:`classmethod` is deprecated,
667
- and support will be removed in the next major version. This is
668
- mostly due to the fact that chaining descriptors, i.e. using
669
- descriptors with :func:`classmethod`, was deprecated in Python 3.11
670
- and removed in Python 3.13. When using :func:`cache_info()`, an
671
- instance method is already required.
673
+ Using :func:`cachedmethod` with :func:`classmethod` is
674
+ deprecated, and support will be removed in the next major
675
+ version. This is mostly due to the fact that chaining
676
+ descriptors, i.e. using descriptors with :func:`classmethod`,
677
+ was deprecated in Python 3.11 and removed in Python 3.13. When
678
+ using :func:`cache_info()`, an instance method is already
679
+ required.
672
680
 
673
- Using :func:`cachedmethod` with an instance that does not provide a
674
- mutable :attr:`__dict__` attribute is deprecated, and may inflict a
675
- noticeable performance penalty. When using :func:`cache_info()`,
676
- :attr:`__dict__` already has to be a mutable mapping.
681
+ Using :func:`cachedmethod` with an instance that does not
682
+ provide a mutable :attr:`__dict__` attribute is deprecated, and
683
+ may inflict a noticeable performance penalty. When using
684
+ :func:`cache_info()`, :attr:`__dict__` already has to be a
685
+ mutable mapping.
677
686
 
678
687
 
679
688
  *****************************************************************
@@ -747,8 +756,9 @@ this module provides several memoizing function decorators with a
747
756
  similar API. All these decorators wrap a function with a memoizing
748
757
  callable that saves up to the `maxsize` most recent calls, using
749
758
  different caching strategies. If `maxsize` is set to :const:`None`,
750
- the caching strategy is effectively disabled and the cache can grow
751
- without bound.
759
+ the size limit is disabled and the cache can grow without bound.
760
+ For :func:`ttl_cache`, the time-to-live still applies, so cached
761
+ results expire after `ttl` even without a size limit.
752
762
 
753
763
  If the optional argument `typed` is set to :const:`True`, function
754
764
  arguments of different types will be cached separately. For example,
@@ -817,7 +827,7 @@ all the decorators in this module are thread-safe by default.
817
827
  .. _cache algorithm: https://en.wikipedia.org/wiki/Cache_algorithms
818
828
  .. _cache stampede: https://en.wikipedia.org/wiki/Cache_stampede
819
829
  .. _condition variable: https://docs.python.org/3/library/threading.html#condition-objects
820
- .. _context manager: https://docs.python.org/dev/glossary.html#term-context-manager
821
- .. _mapping: https://docs.python.org/dev/glossary.html#term-mapping
822
- .. _mutable: https://docs.python.org/dev/glossary.html#term-mutable
830
+ .. _context manager: https://docs.python.org/3/glossary.html#term-context-manager
831
+ .. _mapping: https://docs.python.org/3/glossary.html#term-mapping
832
+ .. _mutable: https://docs.python.org/3/glossary.html#term-mutable
823
833
  .. _thread-safe: https://en.wikipedia.org/wiki/Thread_safety
@@ -27,6 +27,7 @@ classifiers = [
27
27
  "Programming Language :: Python :: 3.12",
28
28
  "Programming Language :: Python :: 3.13",
29
29
  "Programming Language :: Python :: 3.14",
30
+ "Programming Language :: Python :: 3.15",
30
31
  "Topic :: Software Development :: Libraries :: Python Modules",
31
32
  ]
32
33
 
@@ -49,9 +50,6 @@ version = {attr = "cachetools.__version__"}
49
50
 
50
51
  [tool.pyright]
51
52
  typeCheckingMode = "standard"
52
- reportFunctionMemberAccess = "information"
53
- reportOptionalContextManager = "warning"
54
- reportOptionalMemberAccess = "warning"
55
53
 
56
54
  [tool.ruff.lint]
57
55
  ignore = ["DTZ005", "UP031"]
@@ -12,7 +12,7 @@ __all__ = (
12
12
  "cachedmethod",
13
13
  )
14
14
 
15
- __version__ = "7.1.8"
15
+ __version__ = "7.2.1"
16
16
 
17
17
  import collections
18
18
  import collections.abc
@@ -52,7 +52,7 @@ class Cache(collections.abc.MutableMapping):
52
52
  def __init__(self, maxsize, getsizeof=None):
53
53
  if maxsize < 0:
54
54
  raise ValueError("maxsize must be non-negative")
55
- if getsizeof:
55
+ if getsizeof is not None:
56
56
  self.getsizeof = getsizeof
57
57
  if self.getsizeof is not Cache.getsizeof:
58
58
  self.__size = {}
@@ -357,12 +357,11 @@ class RRCache(Cache):
357
357
 
358
358
  def popitem(self):
359
359
  """Remove and return a random `(key, value)` pair."""
360
- try:
361
- key = self.__choice(self.__keys)
362
- except IndexError:
360
+ keys = self.__keys
361
+ if not keys:
363
362
  raise KeyError("%s is empty" % type(self).__name__) from None
364
- else:
365
- return (key, self.pop(key))
363
+ key = self.__choice(keys)
364
+ return (key, self.pop(key))
366
365
 
367
366
  def clear(self):
368
367
  Cache.clear(self)
@@ -1,8 +1,11 @@
1
1
  """Function decorator helpers."""
2
2
 
3
+ # pyright: reportFunctionMemberAccess=false
4
+
3
5
  __all__ = ()
4
6
 
5
7
  import functools
8
+ import warnings
6
9
 
7
10
  # At least for now, the implementation prefers clarity and performance
8
11
  # over ease of maintenance, thus providing separate wrappers for
@@ -227,6 +230,13 @@ def _uncached(func):
227
230
 
228
231
 
229
232
  def _wrapper(func, cache, key, lock=None, cond=None, info=None):
233
+ if cache is None:
234
+ warnings.warn(
235
+ "@cachetools.cached(cache=None) is deprecated",
236
+ DeprecationWarning,
237
+ stacklevel=3,
238
+ )
239
+
230
240
  if info is not None:
231
241
  if cache is None:
232
242
  wrapper = _uncached_info(func, info)
@@ -1,5 +1,7 @@
1
1
  """Method decorator helpers."""
2
2
 
3
+ # pyright: reportOptionalContextManager=false, reportOptionalMemberAccess=false
4
+
3
5
  __all__ = ()
4
6
 
5
7
  import functools
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cachetools
3
- Version: 7.1.8
3
+ Version: 7.2.1
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>
@@ -21,6 +21,7 @@ Classifier: Programming Language :: Python :: 3.11
21
21
  Classifier: Programming Language :: Python :: 3.12
22
22
  Classifier: Programming Language :: Python :: 3.13
23
23
  Classifier: Programming Language :: Python :: 3.14
24
+ Classifier: Programming Language :: Python :: 3.15
24
25
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
25
26
  Requires-Python: >=3.10
26
27
  Description-Content-Type: text/x-rst
@@ -1,4 +1,5 @@
1
1
  import unittest
2
+ import warnings
2
3
 
3
4
  import cachetools
4
5
  import cachetools.keys
@@ -17,6 +18,9 @@ class DecoratorTestMixin(_TestCaseProtocol):
17
18
  self.count = 0
18
19
  return self.count
19
20
 
21
+ def error_func(self, *args, **kwargs):
22
+ raise ValueError("test error")
23
+
20
24
  def test_decorator(self):
21
25
  cache = self.cache(2)
22
26
  wrapper = cachetools.cached(cache)(self.func)
@@ -86,7 +90,7 @@ class DecoratorTestMixin(_TestCaseProtocol):
86
90
  self.assertEqual(wrapper(1), 1)
87
91
  self.assertEqual(lock.count, 5)
88
92
 
89
- def test_decorator_condition(self):
93
+ def test_decorator_cond(self):
90
94
  cache = self.cache(2)
91
95
  lock = cond = CountedCondition()
92
96
  wrapper = cachetools.cached(cache, condition=cond)(self.func)
@@ -105,7 +109,7 @@ class DecoratorTestMixin(_TestCaseProtocol):
105
109
  self.assertEqual(cond.wait_count, 3)
106
110
  self.assertEqual(cond.notify_count, 2)
107
111
 
108
- def test_decorator_lock_condition(self):
112
+ def test_decorator_lock_cond(self):
109
113
  cache = self.cache(2)
110
114
  lock = CountedLock()
111
115
  cond = CountedCondition()
@@ -125,6 +129,26 @@ class DecoratorTestMixin(_TestCaseProtocol):
125
129
  self.assertEqual(cond.wait_count, 3)
126
130
  self.assertEqual(cond.notify_count, 2)
127
131
 
132
+ def test_decorator_cond_error(self):
133
+ cache = self.cache(2)
134
+ lock = cond = CountedCondition()
135
+ wrapper = cachetools.cached(cache, condition=cond)(self.error_func)
136
+
137
+ with self.assertRaises(ValueError):
138
+ wrapper(0)
139
+ self.assertEqual(len(cache), 0)
140
+ self.assertEqual(lock.count, 2)
141
+ self.assertEqual(cond.wait_count, 1)
142
+ self.assertEqual(cond.notify_count, 1)
143
+
144
+ # verify pending set is cleaned up, otherwise this might deadlock
145
+ with self.assertRaises(ValueError):
146
+ wrapper(0)
147
+ self.assertEqual(len(cache), 0)
148
+ self.assertEqual(lock.count, 4)
149
+ self.assertEqual(cond.wait_count, 2)
150
+ self.assertEqual(cond.notify_count, 2)
151
+
128
152
  def test_decorator_wrapped(self):
129
153
  cache = self.cache(2)
130
154
  wrapper = cachetools.cached(cache)(self.func)
@@ -158,7 +182,7 @@ class DecoratorTestMixin(_TestCaseProtocol):
158
182
  self.assertIs(wrapper.cache_lock, lock)
159
183
  self.assertIs(wrapper.cache_condition, None)
160
184
 
161
- def test_decorator_attributes_condition(self):
185
+ def test_decorator_attributes_cond(self):
162
186
  cache = self.cache(2)
163
187
  lock = cond = CountedCondition()
164
188
  wrapper = cachetools.cached(cache, condition=cond)(self.func)
@@ -187,7 +211,7 @@ class DecoratorTestMixin(_TestCaseProtocol):
187
211
  self.assertEqual(len(cache), 0)
188
212
  self.assertEqual(lock.count, 3)
189
213
 
190
- def test_decorator_clear_condition(self):
214
+ def test_decorator_clear_cond(self):
191
215
  cache = self.cache(2)
192
216
  lock = cond = CountedCondition()
193
217
  wrapper = cachetools.cached(cache, condition=cond)(self.func)
@@ -241,7 +265,7 @@ class CacheWrapperTest(unittest.TestCase, DecoratorTestMixin):
241
265
  self.assertEqual(wrapper.cache_info(), (0, 0, 2, 0))
242
266
  self.assertEqual(lock.count, 11)
243
267
 
244
- def test_decorator_condition_info(self):
268
+ def test_decorator_cond_info(self):
245
269
  cache = self.cache(2)
246
270
  lock = cond = CountedCondition()
247
271
  wrapper = cachetools.cached(cache, condition=cond, info=True)(self.func)
@@ -265,7 +289,7 @@ class CacheWrapperTest(unittest.TestCase, DecoratorTestMixin):
265
289
  self.assertEqual(wrapper.cache_info(), (0, 0, 2, 0))
266
290
  self.assertEqual(lock.count, 13)
267
291
 
268
- def test_decorator_lock_condition_info(self):
292
+ def test_decorator_lock_cond_info(self):
269
293
  cache = self.cache(2)
270
294
  lock = CountedLock()
271
295
  cond = CountedCondition()
@@ -310,7 +334,7 @@ class CacheWrapperTest(unittest.TestCase, DecoratorTestMixin):
310
334
  self.assertEqual(len(cache), 0)
311
335
  self.assertEqual(lock.count, 2)
312
336
 
313
- def test_zero_size_cache_decorator_condition(self):
337
+ def test_zero_size_cache_decorator_cond(self):
314
338
  cache = self.cache(0)
315
339
  lock = cond = CountedCondition()
316
340
  wrapper = cachetools.cached(cache, condition=cond)(self.func)
@@ -342,6 +366,20 @@ class CacheWrapperTest(unittest.TestCase, DecoratorTestMixin):
342
366
  self.assertEqual(wrapper.cache_info(), (0, 1, 0, 0))
343
367
  self.assertEqual(lock.count, 4)
344
368
 
369
+ def test_zero_size_cache_decorator_cond_info(self):
370
+ cache = self.cache(0)
371
+ lock = cond = CountedCondition()
372
+ wrapper = cachetools.cached(cache, condition=cond, info=True)(self.func)
373
+
374
+ self.assertEqual(len(cache), 0)
375
+ self.assertEqual(wrapper.cache_info(), (0, 0, 0, 0))
376
+ self.assertEqual(lock.count, 1)
377
+ self.assertEqual(wrapper(0), 0)
378
+ self.assertEqual(len(cache), 0)
379
+ self.assertEqual(lock.count, 4)
380
+ self.assertEqual(wrapper.cache_info(), (0, 1, 0, 0))
381
+ self.assertEqual(lock.count, 5)
382
+
345
383
 
346
384
  class DictWrapperTest(unittest.TestCase, DecoratorTestMixin):
347
385
  def cache(self, minsize):
@@ -367,26 +405,34 @@ class NoneWrapperTest(unittest.TestCase):
367
405
  return args + tuple(kwargs.items())
368
406
 
369
407
  def test_decorator(self):
370
- wrapper = cachetools.cached(None)(self.func)
408
+ with warnings.catch_warnings(record=True) as w:
409
+ wrapper = cachetools.cached(None)(self.func)
410
+ self.assertIs(w[0].category, DeprecationWarning)
371
411
 
372
412
  self.assertEqual(wrapper(0), (0,))
373
413
  self.assertEqual(wrapper(1), (1,))
374
414
  self.assertEqual(wrapper(1, foo="bar"), (1, ("foo", "bar")))
375
415
 
376
416
  def test_decorator_attributes(self):
377
- wrapper = cachetools.cached(None)(self.func)
417
+ with warnings.catch_warnings(record=True) as w:
418
+ wrapper = cachetools.cached(None)(self.func)
419
+ self.assertIs(w[0].category, DeprecationWarning)
378
420
 
379
421
  self.assertIs(wrapper.cache, None)
380
422
  self.assertIs(wrapper.cache_key, cachetools.keys.hashkey)
381
423
  self.assertIs(wrapper.cache_lock, None)
382
424
 
383
425
  def test_decorator_clear(self):
384
- wrapper = cachetools.cached(None)(self.func)
426
+ with warnings.catch_warnings(record=True) as w:
427
+ wrapper = cachetools.cached(None)(self.func)
428
+ self.assertIs(w[0].category, DeprecationWarning)
385
429
 
386
430
  wrapper.cache_clear() # no-op
387
431
 
388
432
  def test_decorator_info(self):
389
- wrapper = cachetools.cached(None, info=True)(self.func)
433
+ with warnings.catch_warnings(record=True) as w:
434
+ wrapper = cachetools.cached(None, info=True)(self.func)
435
+ self.assertIs(w[0].category, DeprecationWarning)
390
436
 
391
437
  self.assertEqual(wrapper.cache_info(), (0, 0, 0, 0))
392
438
  self.assertEqual(wrapper(0), (0,))
@@ -31,6 +31,27 @@ class LFUCacheTest(unittest.TestCase, CacheTestMixin):
31
31
  self.assertEqual(cache[1], 1)
32
32
  self.assertEqual(cache[4], 4)
33
33
 
34
+ def test_lfu_tie(self):
35
+ cache = LFUCache[int, int](maxsize=3)
36
+
37
+ cache[1] = 1
38
+ cache[1]
39
+ cache[2] = 2
40
+ cache[3] = 3
41
+ cache[4] = 4
42
+
43
+ self.assertEqual(len(cache), 3)
44
+ self.assertIn(1, cache)
45
+ self.assertIn(4, cache)
46
+ self.assertTrue(2 in cache or 3 in cache)
47
+ self.assertTrue(2 not in cache or 3 not in cache)
48
+
49
+ cache[5] = 5
50
+ self.assertEqual(len(cache), 3)
51
+ self.assertIn(1, cache)
52
+ self.assertIn(5, cache)
53
+ self.assertTrue(2 in cache or 3 in cache or 4 in cache)
54
+
34
55
  def test_lfu_getsizeof(self):
35
56
  cache = LFUCache[int, int](maxsize=3, getsizeof=lambda x: x)
36
57
 
@@ -86,3 +86,18 @@ class RRCacheTest(unittest.TestCase, CacheTestMixin):
86
86
  def test_rr_default_choice(self):
87
87
  cache = RRCache[int, int](maxsize=2)
88
88
  self.assertIs(cache.choice, random.choice)
89
+
90
+ def test_rr_popitem_empty_custom_choice(self):
91
+ # a custom `choice` function only has to handle non-empty
92
+ # sequences, so an empty cache has to be detected beforehand
93
+ for choice in (min, max, lambda seq: next(iter(seq))):
94
+ cache = RRCache[int, int](maxsize=2, choice=choice)
95
+
96
+ with self.assertRaises(KeyError):
97
+ cache.popitem()
98
+
99
+ cache[1] = 1
100
+ self.assertEqual((1, 1), cache.popitem())
101
+
102
+ with self.assertRaises(KeyError):
103
+ cache.popitem()
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