cachetools 6.2.6__tar.gz → 7.0.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 (35) hide show
  1. {cachetools-6.2.6 → cachetools-7.0.1}/CHANGELOG.rst +34 -0
  2. {cachetools-6.2.6/src/cachetools.egg-info → cachetools-7.0.1}/PKG-INFO +2 -3
  3. {cachetools-6.2.6 → cachetools-7.0.1}/docs/index.rst +76 -54
  4. {cachetools-6.2.6 → cachetools-7.0.1}/pyproject.toml +1 -2
  5. {cachetools-6.2.6 → cachetools-7.0.1}/src/cachetools/__init__.py +20 -14
  6. {cachetools-6.2.6 → cachetools-7.0.1}/src/cachetools/_cached.py +10 -1
  7. cachetools-7.0.1/src/cachetools/_cachedmethod.py +410 -0
  8. {cachetools-6.2.6 → cachetools-7.0.1}/src/cachetools/keys.py +1 -1
  9. {cachetools-6.2.6 → cachetools-7.0.1/src/cachetools.egg-info}/PKG-INFO +2 -3
  10. {cachetools-6.2.6 → cachetools-7.0.1}/src/cachetools.egg-info/SOURCES.txt +1 -0
  11. {cachetools-6.2.6 → cachetools-7.0.1}/tests/__init__.py +28 -0
  12. {cachetools-6.2.6 → cachetools-7.0.1}/tests/test_cached.py +1 -35
  13. cachetools-7.0.1/tests/test_cachedmethod.py +675 -0
  14. cachetools-7.0.1/tests/test_classmethod.py +151 -0
  15. {cachetools-6.2.6 → cachetools-7.0.1}/tests/test_keys.py +1 -1
  16. {cachetools-6.2.6 → cachetools-7.0.1}/tests/test_rr.py +5 -0
  17. {cachetools-6.2.6 → cachetools-7.0.1}/tests/test_threading.py +21 -8
  18. {cachetools-6.2.6 → cachetools-7.0.1}/tests/test_tlru.py +33 -3
  19. {cachetools-6.2.6 → cachetools-7.0.1}/tests/test_ttl.py +14 -0
  20. cachetools-6.2.6/src/cachetools/_cachedmethod.py +0 -128
  21. cachetools-6.2.6/tests/test_cachedmethod.py +0 -434
  22. {cachetools-6.2.6 → cachetools-7.0.1}/LICENSE +0 -0
  23. {cachetools-6.2.6 → cachetools-7.0.1}/MANIFEST.in +0 -0
  24. {cachetools-6.2.6 → cachetools-7.0.1}/README.rst +0 -0
  25. {cachetools-6.2.6 → cachetools-7.0.1}/docs/conf.py +0 -0
  26. {cachetools-6.2.6 → cachetools-7.0.1}/setup.cfg +0 -0
  27. {cachetools-6.2.6 → cachetools-7.0.1}/src/cachetools/func.py +0 -0
  28. {cachetools-6.2.6 → cachetools-7.0.1}/src/cachetools.egg-info/dependency_links.txt +0 -0
  29. {cachetools-6.2.6 → cachetools-7.0.1}/src/cachetools.egg-info/top_level.txt +0 -0
  30. {cachetools-6.2.6 → cachetools-7.0.1}/tests/test_cache.py +0 -0
  31. {cachetools-6.2.6 → cachetools-7.0.1}/tests/test_fifo.py +0 -0
  32. {cachetools-6.2.6 → cachetools-7.0.1}/tests/test_func.py +0 -0
  33. {cachetools-6.2.6 → cachetools-7.0.1}/tests/test_lfu.py +0 -0
  34. {cachetools-6.2.6 → cachetools-7.0.1}/tests/test_lru.py +0 -0
  35. {cachetools-6.2.6 → cachetools-7.0.1}/tox.ini +0 -0
@@ -1,3 +1,37 @@
1
+ v7.0.1 (2026-02-10)
2
+ ===================
3
+
4
+ - Various test improvements.
5
+
6
+ - Update Copilot Instructions.
7
+
8
+
9
+ v7.0.0 (2026-02-01)
10
+ ===================
11
+
12
+ - Require Python 3.10 or later (breaking change).
13
+
14
+ - Drop support for passing ``info`` as fourth positional parameter to
15
+ ``@cached`` (breaking change).
16
+
17
+ - Drop support for ``cache(self)`` returning ``None`` with
18
+ ``@cachedmethod`` (breaking change).
19
+
20
+ - Convert the ``@cachedmethod`` wrappers to descriptors, deprecating
21
+ its use with class methods and instances that do not provide a
22
+ mutable ``__dict__`` attribute (potentially breaking change).
23
+
24
+ - Convert the previously undocumented ``@cachedmethod`` attributes
25
+ (``cache``, ``cache_lock``, etc.) to properties for instance
26
+ methods, providing official support and documentation (potentially
27
+ breaking change).
28
+
29
+ - Add an optional ``info`` parameter to the ``@cachedmethod``
30
+ decorator for reporting per-instance cache statistics. Note that
31
+ this requires the instance's ``__dict__`` attribute to be a mutable
32
+ mapping.
33
+
34
+
1
35
  v6.2.6 (2026-01-27)
2
36
  ===================
3
37
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cachetools
3
- Version: 6.2.6
3
+ Version: 7.0.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>
@@ -11,14 +11,13 @@ Classifier: Intended Audience :: Developers
11
11
  Classifier: Operating System :: OS Independent
12
12
  Classifier: Programming Language :: Python
13
13
  Classifier: Programming Language :: Python :: 3
14
- Classifier: Programming Language :: Python :: 3.9
15
14
  Classifier: Programming Language :: Python :: 3.10
16
15
  Classifier: Programming Language :: Python :: 3.11
17
16
  Classifier: Programming Language :: Python :: 3.12
18
17
  Classifier: Programming Language :: Python :: 3.13
19
18
  Classifier: Programming Language :: Python :: 3.14
20
19
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
- Requires-Python: >=3.9
20
+ Requires-Python: >=3.10
22
21
  Description-Content-Type: text/x-rst
23
22
  License-File: LICENSE
24
23
  Dynamic: license-file
@@ -26,6 +26,7 @@ method calls.
26
26
  from unittest import mock
27
27
  urllib = mock.MagicMock()
28
28
 
29
+ import threading
29
30
  import time
30
31
 
31
32
 
@@ -215,7 +216,6 @@ all cache implementations call :meth:`popitem` to evict items from the
215
216
  cache, this can be achieved by overriding this method in a subclass:
216
217
 
217
218
  .. doctest::
218
- :pyversion: >= 3
219
219
 
220
220
  >>> class MyCache(LRUCache):
221
221
  ... def popitem(self):
@@ -237,7 +237,6 @@ pairs. By overriding :meth:`expire`, a subclass will be able to track
237
237
  expired items:
238
238
 
239
239
  .. doctest::
240
- :pyversion: >= 3
241
240
 
242
241
  >>> class ExpCache(TTLCache):
243
242
  ... def expire(self, time=None):
@@ -260,7 +259,6 @@ method which is called by :meth:`Cache.__getitem__` if the requested
260
259
  key is not found:
261
260
 
262
261
  .. doctest::
263
- :pyversion: >= 3
264
262
 
265
263
  >>> class PepStore(LRUCache):
266
264
  ... def __missing__(self, key):
@@ -364,10 +362,8 @@ often called with the same arguments:
364
362
 
365
363
  .. testcode::
366
364
 
367
- from threading import Lock
368
-
369
365
  # 640K should be enough for anyone...
370
- @cached(cache=LRUCache(maxsize=640*1024, getsizeof=len), lock=Lock())
366
+ @cached(cache=LRUCache(maxsize=640*1024, getsizeof=len), lock=threading.Lock())
371
367
  def get_pep(num):
372
368
  'Retrieve text of a Python Enhancement Proposal'
373
369
  url = 'http://www.python.org/dev/peps/pep-%04d/' % num
@@ -402,7 +398,6 @@ often called with the same arguments:
402
398
  to be enabled explicitly.
403
399
 
404
400
  .. doctest::
405
- :pyversion: >= 3
406
401
 
407
402
  >>> @cached(cache=LRUCache(maxsize=32), info=True)
408
403
  ... def get_pep(num):
@@ -496,7 +491,6 @@ often called with the same arguments:
496
491
  invocations, with separately cached results:
497
492
 
498
493
  .. doctest::
499
- :pyversion: >= 3
500
494
 
501
495
  >>> @cached(LRUCache(maxsize=100))
502
496
  ... def foo(a=1):
@@ -516,7 +510,6 @@ often called with the same arguments:
516
510
  be introduced to avoid ambiguities, e.g.:
517
511
 
518
512
  .. doctest::
519
- :pyversion: >= 3
520
513
 
521
514
  >>> def foo(a=1):
522
515
  ... _foo(a)
@@ -532,17 +525,16 @@ often called with the same arguments:
532
525
  >>> foo(a=1)
533
526
 
534
527
 
535
- .. decorator:: cachedmethod(cache, key=cachetools.keys.methodkey, lock=None, condition=None)
528
+ .. decorator:: cachedmethod(cache, key=cachetools.keys.methodkey, lock=None, condition=None, info=False)
536
529
 
537
- Decorator to wrap a class or instance method with a memoizing
538
- callable that saves results in a (possibly shared) cache.
530
+ Decorator to wrap an instance method with a memoizing callable that
531
+ saves results in a cache.
539
532
 
540
533
  The main difference between this and the :func:`cached` function
541
534
  decorator is that `cache`, `lock` and `condition` are not passed
542
- objects, but functions. Those will be called with :const:`self`
543
- (or :const:`cls` for class methods) as their sole argument to
544
- retrieve the cache, lock, or condition object for the method's
545
- respective instance or class.
535
+ objects, but functions. Those will be called with :const:`self` as
536
+ their sole argument to retrieve a valid cache, lock, or condition
537
+ object for the respective instance.
546
538
 
547
539
  .. note::
548
540
 
@@ -551,55 +543,63 @@ often called with the same arguments:
551
543
  the user's responsibility to handle concurrent calls to the
552
544
  underlying wrapped method in a multithreaded environment.
553
545
 
554
- The `key` function will be called as `key(self, *args, **kwargs)`
555
- to retrieve a suitable cache key. Note that the default `key`
556
- function, :func:`cachetools.keys.methodkey`, ignores its first
557
- argument, i.e. :const:`self`. This has mostly historical reasons,
558
- but also ensures that :const:`self` does not have to be hashable.
546
+ Similarly to the :func:`cached` function decorator, the `cache`,
547
+ `key`, `lock` and `condition` parameters are also available as
548
+ :attr:`cache`, :attr:`cache_key`, :attr:`cache_lock` and
549
+ :attr:`cache_condition` attributes of the wrapped instance method.
550
+ A :func:`cache_clear()` function will also be provided, as well as
551
+ an optional :func:`cache_info()` function reporting per-instance
552
+ cache statistics.
559
553
 
560
- .. note::
561
-
562
- Using :func:`cachedmethod` with a `condition` currently *does*
563
- require :const:`self` be hashable, though.
564
-
565
- You may provide a different `key` function,
566
- e.g. :func:`cachetools.keys.hashkey`, if you need :const:`self` to
567
- be part of the cache key.
554
+ Also, mostly for efficiency, this decorator requires that the
555
+ :attr:`__dict__` attribute on each instance be a mutable mapping.
556
+ This means it will not work with some types, such as metaclasses,
557
+ and those that specify :attr:`__slots__` without including
558
+ :attr:`__dict__` as one of the defined slots.
568
559
 
569
560
  One advantage of :func:`cachedmethod` over the :func:`cached`
570
561
  function decorator is that cache properties such as `maxsize` can
571
562
  be set at runtime:
572
563
 
573
- .. testcode::
564
+ .. doctest::
574
565
 
575
- from cachetools.keys import hashkey
576
- from threading import Lock
566
+ >>> class CachedPEPs:
567
+ ... def __init__(self, cachesize):
568
+ ... self.cache = LRUCache(maxsize=cachesize)
569
+ ... self.lock = threading.Lock()
570
+ ...
571
+ ... @cachedmethod(lambda self: self.cache, lock=lambda self: self.lock, info=True)
572
+ ... def get(self, num):
573
+ ... """Retrieve text of a Python Enhancement Proposal"""
574
+ ... url = 'http://www.python.org/dev/peps/pep-%04d/' % num
575
+ ... with urllib.request.urlopen(url) as s:
576
+ ... return s.read()
577
+
578
+ >>> peps = CachedPEPs(cachesize=32)
579
+ >>> for n in 8, 290, 308, 320, 8, 218, 320, 279, 289, 320, 9991:
580
+ ... pep = peps.get(n)
577
581
 
578
- class CachedPEPs:
582
+ >>> peps.get.cache_info()
583
+ CacheInfo(hits=3, misses=8, maxsize=32, currsize=8)
579
584
 
580
- def __init__(self, cachesize):
581
- self.cache = LRUCache(maxsize=cachesize)
582
- self.lock = Lock()
585
+ >>> peps.get.cache_clear()
583
586
 
584
- @cachedmethod(lambda self: self.cache, lock=lambda self: self.lock)
585
- def get(self, num):
586
- """Retrieve text of a Python Enhancement Proposal"""
587
- url = 'http://www.python.org/dev/peps/pep-%04d/' % num
588
- with urllib.request.urlopen(url) as s:
589
- return s.read()
590
-
591
- peps = CachedPEPs(cachesize=10)
592
- print("PEP #1: %s" % peps.get(1))
587
+ >>> peps.get.cache_info()
588
+ CacheInfo(hits=0, misses=0, maxsize=32, currsize=0)
593
589
 
594
- .. testoutput::
595
- :hide:
596
- :options: +ELLIPSIS
590
+ The `key` function will be called as `key(self, *args, **kwargs)`
591
+ to retrieve a suitable cache key. Note that the default `key`
592
+ function, :func:`cachetools.keys.methodkey`, ignores its first
593
+ argument, i.e. :const:`self`. This has mostly historical reasons,
594
+ but also ensures that :const:`self` does not have to be hashable.
597
595
 
598
- PEP #1: ...
596
+ You may provide a different `key` function,
597
+ e.g. :func:`cachetools.keys.hashkey`, if you need :const:`self` to
598
+ be part of the cache key.
599
599
 
600
600
  When using a shared cache for multiple methods, be aware that
601
601
  different cache keys must be created for each method even when
602
- function arguments are the same, just as with the `@cached`
602
+ function arguments are the same, just as with the :func:`cached`
603
603
  decorator:
604
604
 
605
605
  .. testcode::
@@ -642,11 +642,33 @@ often called with the same arguments:
642
642
  to create distinct cache keys, to avoid issues with
643
643
  :func:`methodkey` skipping its initial `self` argument.
644
644
 
645
- .. deprecated:: 6.0
645
+ .. versionchanged:: 7.0
646
+
647
+ Added the `info` option for reporting per-instance cache
648
+ statistics.
649
+
650
+ :func:`cachedmethod` attributes (:attr:`cache`, :attr:`cache_lock`,
651
+ etc.) are now implemented as properties for instance methods, and
652
+ are finally officially supported.
653
+
654
+ Require :attr:`__dict__` to be a mutable mapping to support
655
+ per-instance :func:`cache_info()`.
656
+
657
+ Returning :const:`None` from `cache(self)` is no longer supported.
658
+
659
+ .. deprecated:: 7.0
660
+
661
+ Using :func:`cachedmethod` with :func:`classmethod` is deprecated,
662
+ and support will be removed in the next major version. This is
663
+ mostly due to the fact that chaining descriptors, i.e. using
664
+ descriptors with :func:`classmethod`, was deprecated in Python 3.11
665
+ and removed in Python 3.13. When using :func:`cache_info()`, an
666
+ instance method is already required.
646
667
 
647
- Support for `cache(self)` returning :const:`None` to suppress any
648
- caching has been deprecated. `cache(self)` should always return a
649
- valid cache object.
668
+ Using :func:`cachedmethod` with an instance that does not provide a
669
+ mutable :attr:`__dict__` attribute is deprecated, and may inflict a
670
+ noticeable performance penalty. When using :func:`cache_info()`,
671
+ :attr:`__dict__` already has to be a mutable mapping.
650
672
 
651
673
 
652
674
  *****************************************************************
@@ -15,14 +15,13 @@ maintainers = [
15
15
  readme = "README.rst"
16
16
  license = "MIT"
17
17
  license-files = ["LICENSE"]
18
- requires-python = ">= 3.9"
18
+ requires-python = ">= 3.10"
19
19
  classifiers = [
20
20
  "Development Status :: 5 - Production/Stable",
21
21
  "Intended Audience :: Developers",
22
22
  "Operating System :: OS Independent",
23
23
  "Programming Language :: Python",
24
24
  "Programming Language :: Python :: 3",
25
- "Programming Language :: Python :: 3.9",
26
25
  "Programming Language :: Python :: 3.10",
27
26
  "Programming Language :: Python :: 3.11",
28
27
  "Programming Language :: Python :: 3.12",
@@ -12,7 +12,7 @@ __all__ = (
12
12
  "cachedmethod",
13
13
  )
14
14
 
15
- __version__ = "6.2.6"
15
+ __version__ = "7.0.1"
16
16
 
17
17
  import collections
18
18
  import collections.abc
@@ -23,8 +23,13 @@ import time
23
23
 
24
24
  from . import keys
25
25
 
26
+ # Typing stubs for this package are provided by typeshed:
27
+ # https://github.com/python/typeshed/tree/main/stubs/cachetools
28
+
26
29
 
27
30
  class _DefaultSize:
31
+ """A minimal "fake" dict that returns a constant size 1 for any key."""
32
+
28
33
  __slots__ = ()
29
34
 
30
35
  def __getitem__(self, _key):
@@ -673,17 +678,6 @@ def cached(cache, key=keys.hashkey, lock=None, condition=None, info=False):
673
678
  """
674
679
  from ._cached import _wrapper
675
680
 
676
- if isinstance(condition, bool):
677
- from warnings import warn
678
-
679
- warn(
680
- "passing `info` as positional parameter is deprecated",
681
- DeprecationWarning,
682
- stacklevel=2,
683
- )
684
- info = condition
685
- condition = None
686
-
687
681
  def decorator(func):
688
682
  if info:
689
683
  if isinstance(cache, Cache):
@@ -708,7 +702,7 @@ def cached(cache, key=keys.hashkey, lock=None, condition=None, info=False):
708
702
  return decorator
709
703
 
710
704
 
711
- def cachedmethod(cache, key=keys.methodkey, lock=None, condition=None):
705
+ def cachedmethod(cache, key=keys.methodkey, lock=None, condition=None, info=False):
712
706
  """Decorator to wrap a class or instance method with a memoizing
713
707
  callable that saves results in a cache.
714
708
 
@@ -716,6 +710,18 @@ def cachedmethod(cache, key=keys.methodkey, lock=None, condition=None):
716
710
  from ._cachedmethod import _wrapper
717
711
 
718
712
  def decorator(method):
719
- return _wrapper(method, cache, key, lock, condition)
713
+ if info:
714
+
715
+ def make_info(cache, hits, misses):
716
+ if isinstance(cache, Cache):
717
+ return _CacheInfo(hits, misses, cache.maxsize, cache.currsize)
718
+ elif isinstance(cache, collections.abc.Mapping):
719
+ return _CacheInfo(hits, misses, None, len(cache))
720
+ else:
721
+ raise TypeError("cache(self) must return a mutable mapping")
722
+
723
+ return _wrapper(method, cache, key, lock, condition, info=make_info)
724
+ else:
725
+ return _wrapper(method, cache, key, lock, condition)
720
726
 
721
727
  return decorator
@@ -1,7 +1,14 @@
1
1
  """Function decorator helpers."""
2
2
 
3
+ __all__ = ()
4
+
3
5
  import functools
4
6
 
7
+ # At least for now, the implementation prefers clarity and performance
8
+ # over ease of maintenance, thus providing separate wrappers for
9
+ # all valid combinations of decorator parameters lock, condition and
10
+ # info.
11
+
5
12
 
6
13
  def _condition_info(func, cache, key, lock, cond, info):
7
14
  hits = misses = 0
@@ -178,7 +185,9 @@ def _locked(func, cache, key, lock):
178
185
  v = func(*args, **kwargs)
179
186
  with lock:
180
187
  try:
181
- # possible race condition: see above
188
+ # In case of a race condition, i.e. if another thread
189
+ # stored a value for this key while we were calling
190
+ # func(), prefer the cached value.
182
191
  return cache.setdefault(k, v)
183
192
  except ValueError:
184
193
  return v # value too large