cachetools 6.2.5__tar.gz → 7.0.0__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.5 → cachetools-7.0.0}/CHANGELOG.rst +38 -0
  2. {cachetools-6.2.5/src/cachetools.egg-info → cachetools-7.0.0}/PKG-INFO +2 -3
  3. {cachetools-6.2.5 → cachetools-7.0.0}/docs/index.rst +84 -61
  4. {cachetools-6.2.5 → cachetools-7.0.0}/pyproject.toml +1 -2
  5. {cachetools-6.2.5 → cachetools-7.0.0}/src/cachetools/__init__.py +23 -15
  6. {cachetools-6.2.5 → cachetools-7.0.0}/src/cachetools/_cached.py +10 -1
  7. cachetools-7.0.0/src/cachetools/_cachedmethod.py +410 -0
  8. {cachetools-6.2.5 → cachetools-7.0.0}/src/cachetools/func.py +3 -0
  9. {cachetools-6.2.5 → cachetools-7.0.0}/src/cachetools/keys.py +7 -4
  10. {cachetools-6.2.5 → cachetools-7.0.0/src/cachetools.egg-info}/PKG-INFO +2 -3
  11. {cachetools-6.2.5 → cachetools-7.0.0}/src/cachetools.egg-info/SOURCES.txt +1 -0
  12. {cachetools-6.2.5 → cachetools-7.0.0}/tests/__init__.py +33 -2
  13. {cachetools-6.2.5 → cachetools-7.0.0}/tests/test_cached.py +1 -35
  14. cachetools-7.0.0/tests/test_cachedmethod.py +675 -0
  15. cachetools-7.0.0/tests/test_classmethod.py +151 -0
  16. {cachetools-6.2.5 → cachetools-7.0.0}/tests/test_keys.py +1 -1
  17. {cachetools-6.2.5 → cachetools-7.0.0}/tests/test_threading.py +21 -8
  18. cachetools-6.2.5/src/cachetools/_cachedmethod.py +0 -128
  19. cachetools-6.2.5/tests/test_cachedmethod.py +0 -434
  20. {cachetools-6.2.5 → cachetools-7.0.0}/LICENSE +0 -0
  21. {cachetools-6.2.5 → cachetools-7.0.0}/MANIFEST.in +0 -0
  22. {cachetools-6.2.5 → cachetools-7.0.0}/README.rst +0 -0
  23. {cachetools-6.2.5 → cachetools-7.0.0}/docs/conf.py +0 -0
  24. {cachetools-6.2.5 → cachetools-7.0.0}/setup.cfg +0 -0
  25. {cachetools-6.2.5 → cachetools-7.0.0}/src/cachetools.egg-info/dependency_links.txt +0 -0
  26. {cachetools-6.2.5 → cachetools-7.0.0}/src/cachetools.egg-info/top_level.txt +0 -0
  27. {cachetools-6.2.5 → cachetools-7.0.0}/tests/test_cache.py +0 -0
  28. {cachetools-6.2.5 → cachetools-7.0.0}/tests/test_fifo.py +0 -0
  29. {cachetools-6.2.5 → cachetools-7.0.0}/tests/test_func.py +0 -0
  30. {cachetools-6.2.5 → cachetools-7.0.0}/tests/test_lfu.py +0 -0
  31. {cachetools-6.2.5 → cachetools-7.0.0}/tests/test_lru.py +0 -0
  32. {cachetools-6.2.5 → cachetools-7.0.0}/tests/test_rr.py +0 -0
  33. {cachetools-6.2.5 → cachetools-7.0.0}/tests/test_tlru.py +0 -0
  34. {cachetools-6.2.5 → cachetools-7.0.0}/tests/test_ttl.py +0 -0
  35. {cachetools-6.2.5 → cachetools-7.0.0}/tox.ini +0 -0
@@ -1,3 +1,41 @@
1
+ v7.0.0 (2026-02-01)
2
+ ===================
3
+
4
+ - Require Python 3.10 or later (breaking change).
5
+
6
+ - Drop support for passing ``info`` as fourth positional parameter to
7
+ ``@cached`` (breaking change).
8
+
9
+ - Drop support for ``cache(self)`` returning ``None`` with
10
+ ``@cachedmethod`` (breaking change).
11
+
12
+ - Convert the ``@cachedmethod`` wrappers to descriptors, deprecating
13
+ its use with class methods and instances that do not provide a
14
+ mutable ``__dict__`` attribute (potentially breaking change).
15
+
16
+ - Convert the previously undocumented ``@cachedmethod`` attributes
17
+ (``cache``, ``cache_lock``, etc.) to properties for instance
18
+ methods, providing official support and documentation (potentially
19
+ breaking change).
20
+
21
+ - Add an optional ``info`` parameter to the ``@cachedmethod``
22
+ decorator for reporting per-instance cache statistics. Note that
23
+ this requires the instance's ``__dict__`` attribute to be a mutable
24
+ mapping.
25
+
26
+
27
+ v6.2.6 (2026-01-27)
28
+ ===================
29
+
30
+ - Improve typedkey performance.
31
+
32
+ - Minor documentation improvements.
33
+
34
+ - Minor testing improvements.
35
+
36
+ - Minor code readability improvements.
37
+
38
+
1
39
  v6.2.5 (2026-01-25)
2
40
  ===================
3
41
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cachetools
3
- Version: 6.2.5
3
+ Version: 7.0.0
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
 
@@ -35,11 +36,11 @@ Cache implementations
35
36
  This module provides several classes implementing caches using
36
37
  different cache algorithms. All these classes derive from class
37
38
  :class:`Cache`, which in turn derives from
38
- :class:`collections.MutableMapping`, and provide :attr:`maxsize` and
39
- :attr:`currsize` properties to retrieve the maximum and current size
40
- of the cache. When a cache is full, :meth:`Cache.__setitem__()` calls
41
- :meth:`self.popitem()` repeatedly until there is enough room for the
42
- item to be added.
39
+ :class:`collections.abc.MutableMapping`, and provide :attr:`maxsize`
40
+ and :attr:`currsize` properties to retrieve the maximum and current
41
+ size of the cache. When a cache is full, :meth:`Cache.__setitem__()`
42
+ calls :meth:`self.popitem()` repeatedly until there is enough room for
43
+ the item to be added.
43
44
 
44
45
  .. note::
45
46
 
@@ -53,8 +54,9 @@ returns the size of a given `value`. The default implementation of
53
54
  :meth:`getsizeof` returns :const:`1` irrespective of its argument,
54
55
  making the cache's size equal to the number of its items, or
55
56
  `len(cache)`. For convenience, all cache classes accept an optional
56
- named constructor parameter `getsizeof`, which may specify a function
57
- of one argument used to retrieve the size of an item's value.
57
+ named constructor parameter `getsizeof`, which may specify a callable
58
+ ``getsizeof(value) -> int`` used to retrieve the size of an item's
59
+ value.
58
60
 
59
61
  The values of a :class:`Cache` are mutable by default, as are e.g. the
60
62
  values of a :class:`dict`. It is the user's responsibility to take
@@ -214,7 +216,6 @@ all cache implementations call :meth:`popitem` to evict items from the
214
216
  cache, this can be achieved by overriding this method in a subclass:
215
217
 
216
218
  .. doctest::
217
- :pyversion: >= 3
218
219
 
219
220
  >>> class MyCache(LRUCache):
220
221
  ... def popitem(self):
@@ -236,7 +237,6 @@ pairs. By overriding :meth:`expire`, a subclass will be able to track
236
237
  expired items:
237
238
 
238
239
  .. doctest::
239
- :pyversion: >= 3
240
240
 
241
241
  >>> class ExpCache(TTLCache):
242
242
  ... def expire(self, time=None):
@@ -259,7 +259,6 @@ method which is called by :meth:`Cache.__getitem__` if the requested
259
259
  key is not found:
260
260
 
261
261
  .. doctest::
262
- :pyversion: >= 3
263
262
 
264
263
  >>> class PepStore(LRUCache):
265
264
  ... def __missing__(self, key):
@@ -363,10 +362,8 @@ often called with the same arguments:
363
362
 
364
363
  .. testcode::
365
364
 
366
- from threading import Lock
367
-
368
365
  # 640K should be enough for anyone...
369
- @cached(cache=LRUCache(maxsize=640*1024, getsizeof=len), lock=Lock())
366
+ @cached(cache=LRUCache(maxsize=640*1024, getsizeof=len), lock=threading.Lock())
370
367
  def get_pep(num):
371
368
  'Retrieve text of a Python Enhancement Proposal'
372
369
  url = 'http://www.python.org/dev/peps/pep-%04d/' % num
@@ -401,7 +398,6 @@ often called with the same arguments:
401
398
  to be enabled explicitly.
402
399
 
403
400
  .. doctest::
404
- :pyversion: >= 3
405
401
 
406
402
  >>> @cached(cache=LRUCache(maxsize=32), info=True)
407
403
  ... def get_pep(num):
@@ -495,7 +491,6 @@ often called with the same arguments:
495
491
  invocations, with separately cached results:
496
492
 
497
493
  .. doctest::
498
- :pyversion: >= 3
499
494
 
500
495
  >>> @cached(LRUCache(maxsize=100))
501
496
  ... def foo(a=1):
@@ -515,7 +510,6 @@ often called with the same arguments:
515
510
  be introduced to avoid ambiguities, e.g.:
516
511
 
517
512
  .. doctest::
518
- :pyversion: >= 3
519
513
 
520
514
  >>> def foo(a=1):
521
515
  ... _foo(a)
@@ -531,17 +525,16 @@ often called with the same arguments:
531
525
  >>> foo(a=1)
532
526
 
533
527
 
534
- .. 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)
535
529
 
536
- Decorator to wrap a class or instance method with a memoizing
537
- 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.
538
532
 
539
533
  The main difference between this and the :func:`cached` function
540
534
  decorator is that `cache`, `lock` and `condition` are not passed
541
- objects, but functions. Those will be called with :const:`self`
542
- (or :const:`cls` for class methods) as their sole argument to
543
- retrieve the cache, lock, or condition object for the method's
544
- 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.
545
538
 
546
539
  .. note::
547
540
 
@@ -550,55 +543,63 @@ often called with the same arguments:
550
543
  the user's responsibility to handle concurrent calls to the
551
544
  underlying wrapped method in a multithreaded environment.
552
545
 
553
- The `key` function will be called as `key(self, *args, **kwargs)`
554
- to retrieve a suitable cache key. Note that the default `key`
555
- function, :func:`cachetools.keys.methodkey`, ignores its first
556
- argument, i.e. :const:`self`. This has mostly historical reasons,
557
- 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.
558
553
 
559
- .. note::
560
-
561
- Using :func:`cachedmethod` with a `condition` currently *does*
562
- require :const:`self` be hashable, though.
563
-
564
- You may provide a different `key` function,
565
- e.g. :func:`cachetools.keys.hashkey`, if you need :const:`self` to
566
- 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.
567
559
 
568
560
  One advantage of :func:`cachedmethod` over the :func:`cached`
569
561
  function decorator is that cache properties such as `maxsize` can
570
562
  be set at runtime:
571
563
 
572
- .. testcode::
564
+ .. doctest::
573
565
 
574
- from cachetools.keys import hashkey
575
- 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)
576
581
 
577
- class CachedPEPs:
582
+ >>> peps.get.cache_info()
583
+ CacheInfo(hits=3, misses=8, maxsize=32, currsize=8)
578
584
 
579
- def __init__(self, cachesize):
580
- self.cache = LRUCache(maxsize=cachesize)
581
- self.lock = Lock()
585
+ >>> peps.get.cache_clear()
582
586
 
583
- @cachedmethod(lambda self: self.cache, lock=lambda self: self.lock)
584
- def get(self, num):
585
- """Retrieve text of a Python Enhancement Proposal"""
586
- url = 'http://www.python.org/dev/peps/pep-%04d/' % num
587
- with urllib.request.urlopen(url) as s:
588
- return s.read()
589
-
590
- peps = CachedPEPs(cachesize=10)
591
- print("PEP #1: %s" % peps.get(1))
587
+ >>> peps.get.cache_info()
588
+ CacheInfo(hits=0, misses=0, maxsize=32, currsize=0)
592
589
 
593
- .. testoutput::
594
- :hide:
595
- :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.
596
595
 
597
- 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.
598
599
 
599
600
  When using a shared cache for multiple methods, be aware that
600
601
  different cache keys must be created for each method even when
601
- function arguments are the same, just as with the `@cached`
602
+ function arguments are the same, just as with the :func:`cached`
602
603
  decorator:
603
604
 
604
605
  .. testcode::
@@ -641,11 +642,33 @@ often called with the same arguments:
641
642
  to create distinct cache keys, to avoid issues with
642
643
  :func:`methodkey` skipping its initial `self` argument.
643
644
 
644
- .. 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.
645
667
 
646
- Support for `cache(self)` returning :const:`None` to suppress any
647
- caching has been deprecated. `cache(self)` should always return a
648
- 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.
649
672
 
650
673
 
651
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.5"
15
+ __version__ = "7.0.0"
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):
@@ -540,6 +545,8 @@ class TTLCache(_TimedCache):
540
545
  class TLRUCache(_TimedCache):
541
546
  """Time aware Least Recently Used (TLRU) cache implementation."""
542
547
 
548
+ __HEAP_CLEANUP_FACTOR = 2 # clean up the heap if size > N * len(items)
549
+
543
550
  @functools.total_ordering
544
551
  class _Item:
545
552
  __slots__ = ("key", "expires", "removed")
@@ -625,7 +632,7 @@ class TLRUCache(_TimedCache):
625
632
  items = self.__items
626
633
  order = self.__order
627
634
  # clean up the heap if too many items are marked as removed
628
- if len(order) > len(items) * 2:
635
+ if len(order) > len(items) * self.__HEAP_CLEANUP_FACTOR:
629
636
  self.__order = order = [item for item in order if not item.removed]
630
637
  heapq.heapify(order)
631
638
  expired = []
@@ -671,17 +678,6 @@ def cached(cache, key=keys.hashkey, lock=None, condition=None, info=False):
671
678
  """
672
679
  from ._cached import _wrapper
673
680
 
674
- if isinstance(condition, bool):
675
- from warnings import warn
676
-
677
- warn(
678
- "passing `info` as positional parameter is deprecated",
679
- DeprecationWarning,
680
- stacklevel=2,
681
- )
682
- info = condition
683
- condition = None
684
-
685
681
  def decorator(func):
686
682
  if info:
687
683
  if isinstance(cache, Cache):
@@ -706,7 +702,7 @@ def cached(cache, key=keys.hashkey, lock=None, condition=None, info=False):
706
702
  return decorator
707
703
 
708
704
 
709
- def cachedmethod(cache, key=keys.methodkey, lock=None, condition=None):
705
+ def cachedmethod(cache, key=keys.methodkey, lock=None, condition=None, info=False):
710
706
  """Decorator to wrap a class or instance method with a memoizing
711
707
  callable that saves results in a cache.
712
708
 
@@ -714,6 +710,18 @@ def cachedmethod(cache, key=keys.methodkey, lock=None, condition=None):
714
710
  from ._cachedmethod import _wrapper
715
711
 
716
712
  def decorator(method):
717
- 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)
718
726
 
719
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