cachetools 7.2.8__tar.gz → 8.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 (43) hide show
  1. cachetools-8.0.0/AGENTS.md +99 -0
  2. {cachetools-7.2.8 → cachetools-8.0.0}/CHANGELOG.rst +45 -0
  3. {cachetools-7.2.8/src/cachetools.egg-info → cachetools-8.0.0}/PKG-INFO +12 -9
  4. {cachetools-7.2.8 → cachetools-8.0.0}/README.rst +10 -6
  5. {cachetools-7.2.8 → cachetools-8.0.0}/docs/conf.py +0 -1
  6. {cachetools-7.2.8 → cachetools-8.0.0}/docs/index.rst +76 -32
  7. {cachetools-7.2.8 → cachetools-8.0.0}/pyproject.toml +1 -2
  8. {cachetools-7.2.8 → cachetools-8.0.0}/src/cachetools/__init__.py +26 -44
  9. {cachetools-7.2.8 → cachetools-8.0.0}/src/cachetools/__init__.pyi +27 -27
  10. cachetools-8.0.0/src/cachetools/_cached.py +104 -0
  11. cachetools-8.0.0/src/cachetools/_cachedmethod.py +139 -0
  12. cachetools-8.0.0/src/cachetools/_decorator.py +216 -0
  13. {cachetools-7.2.8 → cachetools-8.0.0}/src/cachetools/_descriptor.py +52 -79
  14. {cachetools-7.2.8 → cachetools-8.0.0}/src/cachetools/func.pyi +2 -2
  15. cachetools-8.0.0/src/cachetools/keys.py +61 -0
  16. {cachetools-7.2.8 → cachetools-8.0.0/src/cachetools.egg-info}/PKG-INFO +12 -9
  17. {cachetools-7.2.8 → cachetools-8.0.0}/src/cachetools.egg-info/SOURCES.txt +2 -1
  18. {cachetools-7.2.8 → cachetools-8.0.0}/tests/test_cached.py +163 -31
  19. {cachetools-7.2.8 → cachetools-8.0.0}/tests/test_cachedmethod.py +129 -65
  20. {cachetools-7.2.8 → cachetools-8.0.0}/tests/test_func.py +65 -0
  21. {cachetools-7.2.8 → cachetools-8.0.0}/tests/test_keys.py +0 -2
  22. {cachetools-7.2.8 → cachetools-8.0.0}/tests/test_lfu.py +41 -13
  23. {cachetools-7.2.8 → cachetools-8.0.0}/tests/test_tlru.py +2 -2
  24. {cachetools-7.2.8 → cachetools-8.0.0}/tests/test_ttl.py +2 -2
  25. cachetools-7.2.8/src/cachetools/_cached.py +0 -284
  26. cachetools-7.2.8/src/cachetools/_cachedmethod.py +0 -344
  27. cachetools-7.2.8/src/cachetools/keys.py +0 -77
  28. cachetools-7.2.8/tests/test_classmethod.py +0 -194
  29. {cachetools-7.2.8 → cachetools-8.0.0}/LICENSE +0 -0
  30. {cachetools-7.2.8 → cachetools-8.0.0}/MANIFEST.in +0 -0
  31. {cachetools-7.2.8 → cachetools-8.0.0}/setup.cfg +0 -0
  32. {cachetools-7.2.8 → cachetools-8.0.0}/src/cachetools/func.py +0 -0
  33. {cachetools-7.2.8 → cachetools-8.0.0}/src/cachetools/keys.pyi +0 -0
  34. {cachetools-7.2.8 → cachetools-8.0.0}/src/cachetools/py.typed +0 -0
  35. {cachetools-7.2.8 → cachetools-8.0.0}/src/cachetools.egg-info/dependency_links.txt +0 -0
  36. {cachetools-7.2.8 → cachetools-8.0.0}/src/cachetools.egg-info/top_level.txt +0 -0
  37. {cachetools-7.2.8 → cachetools-8.0.0}/tests/__init__.py +0 -0
  38. {cachetools-7.2.8 → cachetools-8.0.0}/tests/test_cache.py +0 -0
  39. {cachetools-7.2.8 → cachetools-8.0.0}/tests/test_fifo.py +0 -0
  40. {cachetools-7.2.8 → cachetools-8.0.0}/tests/test_lru.py +0 -0
  41. {cachetools-7.2.8 → cachetools-8.0.0}/tests/test_rr.py +0 -0
  42. {cachetools-7.2.8 → cachetools-8.0.0}/tests/test_threading.py +0 -0
  43. {cachetools-7.2.8 → cachetools-8.0.0}/tox.ini +0 -0
@@ -0,0 +1,99 @@
1
+ # cachetools
2
+
3
+ Memoizing collections and decorators, including `functools.lru_cache` variants. Pure
4
+ Python ≥ 3.11, no runtime dependencies, `src/` layout, inline type stubs (`*.pyi`,
5
+ `py.typed`). Single maintainer; `master` is the release branch, `dev/vX.Y.Z` branches
6
+ prepare bugfix releases and `develop` (the 8.0 line) is rebased onto the latest one.
7
+ Code reviews follow `.github/copilot-instructions.md`.
8
+
9
+ ## Commands
10
+
11
+ Run everything through `tox`, never bare `pytest`, `ruff` or `pyright`:
12
+
13
+ ```bash
14
+ tox -p auto # all environments, as CI does
15
+ tox -e py -- tests/test_ttl.py -k expire # tests; posargs go to pytest
16
+ tox -e pyright,ruff,ruff-format,docs,doctest # stubs, lint, format, docs
17
+ ```
18
+
19
+ CI (GitHub Actions on `tkem/cachetools`, every push) runs CPython 3.11–3.15,
20
+ free-threaded 3.13t–3.15t and PyPy 3.11; without `gh`, check runs via
21
+ `https://api.github.com/repos/tkem/cachetools/actions/runs?branch=<branch>`. Branch
22
+ coverage is 100 %; on 3.13+ only the pre-3.13 `@classmethod` line in
23
+ `_MethodDescriptor.__get__` is uncovered.
24
+
25
+ ## Code Style
26
+
27
+ - No annotations in `.py` files; types live in the `.pyi` stubs. Decorator
28
+ `@overload`s come in the order keyword-only `info: Literal[True]`,
29
+ `info: Literal[False] = ...` (default), positional `Literal[True]`.
30
+ - Prefer clarity and performance over DRY: one wrapper class per lock/condition/info
31
+ combination in `_decorator.py`, `_cached.py` and `_cachedmethod.py`, and the key
32
+ functions in `keys.py` are written out in full. Apply a change to **all** variants.
33
+ - Keep hot paths free of avoidable Python-level calls. Look links and items up with
34
+ `.get()` and test for `None` instead of catching `KeyError` per new key (~200 ns).
35
+ - Wrapper `__call__` methods, `_Unbound.__call__`, `_MethodDescriptor.__call__` and
36
+ the key functions take their receiver positional-only (`self, /`), so wrapped
37
+ callables may take a keyword argument named `self`; keep it that way everywhere.
38
+ - Stubs: never annotate `self` in an `__init__` overload with class-scoped type
39
+ variables (pyright warns, and `tox` does not fail on warnings, so check for
40
+ `0 warnings`); an overload without `timer` relies on the `_TT` default instead.
41
+ - Unsupported use raises `TypeError` before anything is cached, chained `from None`;
42
+ a store that fails with `ValueError` (value too large) is swallowed and the
43
+ computed value returned.
44
+ - Docs (`docs/index.rst`, doctested): wrap prose at column 70, two spaces after a
45
+ sentence, single backticks for parameters and `:meth:`/`:func:`/`:exc:` roles. Keep
46
+ `README.rst` examples in sync with it.
47
+
48
+ ## Architecture Constraints
49
+
50
+ - A value's size must not change after insertion. With the default `getsizeof` the
51
+ size dict is `None` and `getsizeof()` is never called: keep that fast path.
52
+ - Cache subclasses override `__setitem__`, `__delitem__`, `popitem()`, `clear()` and
53
+ optionally `__getitem__`; after the parent `__getitem__`, check the subclass's own
54
+ bookkeeping, not `key in self`, since `__missing__` may not store the item.
55
+ `popitem()` evicts via `del self[key]`, so `__delitem__` overrides see evictions.
56
+ - `items()` and `values()` read through `__getitem__` and count as a use of every item;
57
+ inspect state with `list(cache)`. `LFUCache` ties discard the least recently used key
58
+ (documented since 7.3) in O(1): keep the `OrderedDict` per use count (#461).
59
+ - Timed caches expire lazily (`__len__`, `__repr__` and `currsize` call `expire()`,
60
+ `clear()` does not); every insert and `popitem()` must keep calling that hook.
61
+ - Decorator wrappers are objects, not functions; `_WrapperBase` runs
62
+ `update_wrapper()` *before* setting its public attributes (stacked decorators).
63
+ - A `condition` passed with a separate `lock` must be created with that lock
64
+ (`Condition(lock)`), as `wait()` and `notify_all()` run under `lock`; not checked.
65
+ - `@cachedmethod` stores per-instance wrappers in `obj.__dict__`; `super()` lookups and
66
+ lookups after `del Cls.method` use `obj.__dict__["__cachetools_cachedmethods"]`.
67
+ `cache_key` is `partial(key, obj)`, not `types.MethodType`, which on PyPy accepts a
68
+ non-callable `key`. Pickling and deep-copying such objects (#417) and
69
+ `create_autospec()` on their classes (#443) must keep working;
70
+ `_MethodDescriptor._lookup` must stay importable.
71
+ - Key functions return plain tuples, `args` itself without kwargs; `_HashedTuple`
72
+ survives only as the kwargs separator and to load keys pickled by 7.x.
73
+
74
+ ## Testing
75
+
76
+ - New behavior needs tests under `tests/`; user-visible features need docs in
77
+ `docs/index.rst`. One `TestCase` per cache type on `CacheTestMixin`; TTL tests use a
78
+ manual `Timer`. Objects pickled by name must be reachable from module level.
79
+ - Stub checks need concrete annotated tests without `# type: ignore`, since
80
+ `test_func.py` calls decorators via `Callable[..., Any]`.
81
+ - Threading tests use real `Lock`/`Condition` objects (`WaitCondition(lock)` forces a
82
+ contended `wait()`), join with `TIMEOUT` and assert the thread finished.
83
+ - Performance: `timeit`, best of 7, on a pinned core (`taskset`), never in a VM,
84
+ against a git worktree of the previous release. Never trade documented subclass
85
+ hooks or semantics for speed.
86
+
87
+ ## Boundaries
88
+
89
+ - Never edit `CHANGELOG.rst` or bump `__version__`; both are release-only.
90
+ - Never promise cross-version pickle compatibility or build on the byte format; any
91
+ release, even a bugfix, may change it.
92
+ - Do not re-raise the *Maintainer Decisions* in `.github/copilot-instructions.md`.
93
+
94
+ ## Git
95
+
96
+ - PRs reference an open issue (except trivial fixes), in separate commits:
97
+ `Fix #123: ...`, `Tests for #123: ...`, `Docs for #123: ...`.
98
+ - Run `tox -p auto` before pushing. Update this file in the same PR that changes a
99
+ convention, and keep it under 100 lines.
@@ -1,3 +1,48 @@
1
+ v8.0.0 (2026-11-06)
2
+ ===================
3
+
4
+ - Drop Python 3.10 support (breaking change).
5
+
6
+ - Drop support for deprecated ``@cachedmethod`` features (breaking
7
+ change).
8
+
9
+ - Drop support for ``@cached(cache=None)`` (breaking change).
10
+
11
+ - Require custom cache classes to support ``__len__`` when used with
12
+ ``info=True`` (breaking change).
13
+
14
+ - Refactor ``_CacheInfo`` and make it public as ``CacheInfo``
15
+ (potentially breaking change).
16
+
17
+ - Return callable objects instead of functions from ``@cached`` and
18
+ the ``cachetools.func`` decorators (potentially breaking change).
19
+
20
+ - Do not set ``cache_info`` to ``None`` on ``@cached`` wrappers if
21
+ ``info`` is not set (potentially breaking change).
22
+
23
+ - Raise ``TypeError`` for ``@cachedmethod`` descriptors assigned
24
+ after class creation, i.e. without ``__set_name__`` being called
25
+ (potentially breaking change).
26
+
27
+ - Various performance improvements.
28
+
29
+ - Various documentation improvements.
30
+
31
+ - Various test improvements.
32
+
33
+
34
+ v7.3.0 (2026-11-05)
35
+ ===================
36
+
37
+ - Use ``OrderedDict`` for defined and faster ``LFUCache`` eviction.
38
+
39
+ - Various unit test improvements.
40
+
41
+ - Minor type stub improvements.
42
+
43
+ - Minor documentation improvements.
44
+
45
+
1
46
  v7.2.8 (2026-10-31)
2
47
  ===================
3
48
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cachetools
3
- Version: 7.2.8
3
+ Version: 8.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>
@@ -16,14 +16,13 @@ Classifier: Intended Audience :: Developers
16
16
  Classifier: Operating System :: OS Independent
17
17
  Classifier: Programming Language :: Python
18
18
  Classifier: Programming Language :: Python :: 3
19
- Classifier: Programming Language :: Python :: 3.10
20
19
  Classifier: Programming Language :: Python :: 3.11
21
20
  Classifier: Programming Language :: Python :: 3.12
22
21
  Classifier: Programming Language :: Python :: 3.13
23
22
  Classifier: Programming Language :: Python :: 3.14
24
23
  Classifier: Programming Language :: Python :: 3.15
25
24
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
26
- Requires-Python: >=3.10
25
+ Requires-Python: >=3.11
27
26
  Description-Content-Type: text/x-rst
28
27
  License-File: LICENSE
29
28
  Dynamic: license-file
@@ -67,14 +66,18 @@ function decorator.
67
66
  # cache least recently used Python Enhancement Proposals
68
67
  @cached(cache=LRUCache(maxsize=32))
69
68
  def get_pep(num):
70
- url = 'http://www.python.org/dev/peps/pep-%04d/' % num
71
- with urllib.request.urlopen(url) as s:
69
+ from urllib.request import urlopen
70
+ url = 'https://peps.python.org/pep-%04d/' % num
71
+ with urlopen(url) as s:
72
72
  return s.read()
73
73
 
74
- # cache weather data for no longer than ten minutes
74
+ # cache PyOWM weather data for no longer than ten minutes
75
+ from pyowm.owm import OWM
76
+ owm = OWM('your-free-api-key')
77
+
75
78
  @cached(cache=TTLCache(maxsize=1024, ttl=600))
76
79
  def get_weather(place):
77
- return owm.weather_at_place(place).get_weather()
80
+ return owm.weather_manager().weather_at_place(place).weather
78
81
 
79
82
  For the purpose of this module, a *cache* is a mutable_ mapping_ of a
80
83
  fixed maximum size. When the cache is full, i.e. by adding another
@@ -124,8 +127,8 @@ Licensed under the `MIT License`_.
124
127
 
125
128
 
126
129
  .. _@lru_cache: https://docs.python.org/3/library/functools.html#functools.lru_cache
127
- .. _mutable: https://docs.python.org/dev/glossary.html#term-mutable
128
- .. _mapping: https://docs.python.org/dev/glossary.html#term-mapping
130
+ .. _mutable: https://docs.python.org/3/glossary.html#term-mutable
131
+ .. _mapping: https://docs.python.org/3/glossary.html#term-mapping
129
132
  .. _cache algorithm: https://en.wikipedia.org/wiki/Cache_algorithms
130
133
 
131
134
  .. _PyPI: https://pypi.org/project/cachetools/
@@ -37,14 +37,18 @@ function decorator.
37
37
  # cache least recently used Python Enhancement Proposals
38
38
  @cached(cache=LRUCache(maxsize=32))
39
39
  def get_pep(num):
40
- url = 'http://www.python.org/dev/peps/pep-%04d/' % num
41
- with urllib.request.urlopen(url) as s:
40
+ from urllib.request import urlopen
41
+ url = 'https://peps.python.org/pep-%04d/' % num
42
+ with urlopen(url) as s:
42
43
  return s.read()
43
44
 
44
- # cache weather data for no longer than ten minutes
45
+ # cache PyOWM weather data for no longer than ten minutes
46
+ from pyowm.owm import OWM
47
+ owm = OWM('your-free-api-key')
48
+
45
49
  @cached(cache=TTLCache(maxsize=1024, ttl=600))
46
50
  def get_weather(place):
47
- return owm.weather_at_place(place).get_weather()
51
+ return owm.weather_manager().weather_at_place(place).weather
48
52
 
49
53
  For the purpose of this module, a *cache* is a mutable_ mapping_ of a
50
54
  fixed maximum size. When the cache is full, i.e. by adding another
@@ -94,8 +98,8 @@ Licensed under the `MIT License`_.
94
98
 
95
99
 
96
100
  .. _@lru_cache: https://docs.python.org/3/library/functools.html#functools.lru_cache
97
- .. _mutable: https://docs.python.org/dev/glossary.html#term-mutable
98
- .. _mapping: https://docs.python.org/dev/glossary.html#term-mapping
101
+ .. _mutable: https://docs.python.org/3/glossary.html#term-mutable
102
+ .. _mapping: https://docs.python.org/3/glossary.html#term-mapping
99
103
  .. _cache algorithm: https://en.wikipedia.org/wiki/Cache_algorithms
100
104
 
101
105
  .. _PyPI: https://pypi.org/project/cachetools/
@@ -13,6 +13,5 @@ extensions = [
13
13
  "sphinx.ext.todo",
14
14
  ]
15
15
  exclude_patterns = ["_build"]
16
- master_doc = "index"
17
16
  html_theme = "classic"
18
17
  intersphinx_mapping = {"python": ("https://docs.python.org/3", None)}
@@ -44,15 +44,15 @@ calls :meth:`self.popitem()` repeatedly until there is enough room for
44
44
  the item to be added.
45
45
 
46
46
  In general, a cache's size is the total size of its items' values.
47
- Therefore, :class:`Cache` provides a :meth:`~Cache.getsizeof` method, which
48
- returns the size of a given `value`. The default implementation of
49
- :meth:`~Cache.getsizeof` returns :const:`1` irrespective of its argument,
50
- making the cache's size equal to the number of its items, or
47
+ Therefore, :class:`Cache` provides a :meth:`~Cache.getsizeof` method,
48
+ which returns the size of a given `value`. The default implementation
49
+ of :meth:`~Cache.getsizeof` returns :const:`1` irrespective of its
50
+ argument, making the cache's size equal to the number of its items, or
51
51
  `len(cache)`. For convenience, all cache classes accept an optional
52
52
  named constructor parameter `getsizeof`, which may specify a callable
53
- ``getsizeof(value)`` returning a non-negative number as the size of
54
- an item's value. An item whose size exceeds :attr:`~Cache.maxsize` cannot be
55
- stored, and trying to do so raises :exc:`ValueError`.
53
+ ``getsizeof(value)`` returning a non-negative number as the size of an
54
+ item's value. An item whose size exceeds :attr:`~Cache.maxsize`
55
+ cannot be stored, and trying to do so raises :exc:`ValueError`.
56
56
 
57
57
  The values of a :class:`Cache` are mutable by default, as are e.g. the
58
58
  values of a :class:`dict`. It is the user's responsibility to take
@@ -78,6 +78,16 @@ inserted into the cache.
78
78
  additionally need to override :meth:`__getitem__`,
79
79
  :meth:`__setitem__` and :meth:`__delitem__`.
80
80
 
81
+ .. note::
82
+
83
+ Even though :class:`Cache` and the subclasses provided by this
84
+ module can generally be `pickled`_, the byte stream format is an
85
+ implementation detail that *any* release may change, even a
86
+ bugfix release. Therefore, do not rely on being able to
87
+ unpickle a cache, e.g. one stored in a file or database, with a
88
+ different :mod:`cachetools` version than the one it was pickled
89
+ with.
90
+
81
91
  .. autoclass:: FIFOCache(maxsize, getsizeof=None)
82
92
  :members: popitem
83
93
 
@@ -92,10 +102,17 @@ inserted into the cache.
92
102
  :members: popitem
93
103
 
94
104
  This class counts how often an item is retrieved, and discards the
95
- items used least often to make space when necessary.
105
+ items used least often to make space when necessary. In case of a
106
+ tie, the least recently used of these items is discarded.
107
+
108
+ Inserting or updating a cache item via :meth:`LFUCache.__setitem__`
109
+ also counts as a use of the item; an update will increment the
110
+ item's use count.
111
+
112
+ .. versionchanged:: 7.3
96
113
 
97
- Updating a cache item via :meth:`LFUCache.__setitem__` also counts
98
- as a use of the item, i.e. it will increment the item's use count.
114
+ Switched to discarding the least recently used item in case of
115
+ a tie. Previously, an arbitrary item was discarded.
99
116
 
100
117
  .. autoclass:: LRUCache(maxsize, getsizeof=None)
101
118
  :members: popitem
@@ -397,9 +414,10 @@ often called with the same arguments:
397
414
  get_pep.cache_clear()
398
415
 
399
416
  If `info` is set to :const:`True`, the wrapped function is
400
- instrumented with a :func:`cache_info()` function that returns a
401
- named tuple showing `hits`, `misses`, `maxsize` and `currsize`, to
402
- help measure the effectiveness of the cache.
417
+ instrumented with a :meth:`cache_info()` function that returns a
418
+ :class:`CacheInfo` instance, a named tuple with fields `hits`,
419
+ `misses`, `maxsize` and `currsize`, to help measure the
420
+ effectiveness of the cache.
403
421
 
404
422
  .. note::
405
423
 
@@ -533,12 +551,10 @@ often called with the same arguments:
533
551
  >>> foo(1)
534
552
  >>> foo(a=1)
535
553
 
536
- .. deprecated:: 7.2
554
+ .. versionchanged:: 8.0
537
555
 
538
- Passing :const:`None` as `cache` parameter is deprecated, and
539
- support will be removed in the next major version. This is
540
- mainly for consistency with :func:`cachedmethod`, which no
541
- longer supports returning :const:`None` from `cache(self)`.
556
+ Passing :const:`None` as the `cache` parameter is no longer
557
+ supported, and now raises a :exc:`TypeError`.
542
558
 
543
559
 
544
560
  .. decorator:: cachedmethod(cache, key=cachetools.keys.methodkey, lock=None, condition=None, info=False)
@@ -624,6 +640,17 @@ often called with the same arguments:
624
640
  e.g. :func:`cachetools.keys.hashkey`, if you need `self` to be part
625
641
  of the cache key.
626
642
 
643
+ Accessing a decorated method through its class, e.g.
644
+ ``Cls.method``, yields a callable that takes the instance as its
645
+ first positional argument, or as a keyword argument named after the
646
+ wrapped method's first parameter. Unlike the method of an
647
+ instance, this callable does not provide the :attr:`cache`,
648
+ :attr:`cache_lock`, etc. attributes or the :meth:`cache_clear()`
649
+ and :meth:`cache_info()` functions, so this usage pattern is not
650
+ encouraged. Note also that pickling or deep-copying an instance
651
+ restores its methods with fresh :meth:`cache_info()` statistics,
652
+ i.e. with the hit and miss counts reset to zero.
653
+
627
654
  When using a shared cache for multiple methods, be aware that
628
655
  different cache keys must be created for each method even when
629
656
  function arguments are the same, just as with the :func:`cached`
@@ -667,7 +694,8 @@ often called with the same arguments:
667
694
 
668
695
  Note how keyword arguments are used with :func:`functools.partial`
669
696
  to create distinct cache keys, to avoid issues with
670
- :func:`~cachetools.keys.methodkey` skipping its initial `self` argument.
697
+ :func:`~cachetools.keys.methodkey` skipping its initial `self`
698
+ argument.
671
699
 
672
700
  .. versionchanged:: 7.0
673
701
 
@@ -681,23 +709,38 @@ often called with the same arguments:
681
709
  Require :attr:`__dict__` to be a mutable mapping to support
682
710
  per-instance :meth:`cache_info()`.
683
711
 
684
- Returning :const:`None` from `cache(self)` is no longer supported.
712
+ Returning :const:`None` from `cache(self)` is no longer
713
+ supported.
685
714
 
686
- .. deprecated:: 7.0
715
+ .. versionchanged:: 8.0
687
716
 
688
- Using :func:`cachedmethod` with :func:`classmethod` is
689
- deprecated, and support will be removed in the next major
690
- version. This is mostly due to the fact that chaining
691
- descriptors, i.e. using descriptors with :func:`classmethod`,
692
- was deprecated in Python 3.11 and removed in Python 3.13. When
693
- using :func:`cache_info()`, an instance method is already
694
- required.
717
+ Using :func:`cachedmethod` with :func:`classmethod` is no longer
718
+ supported, and now raises a :exc:`TypeError`. This is mostly
719
+ due to the fact that chaining descriptors, i.e. using
720
+ descriptors with :func:`classmethod`, was deprecated in Python
721
+ 3.11 and removed in Python 3.13.
695
722
 
696
723
  Using :func:`cachedmethod` with an instance that does not
697
- provide a mutable :attr:`__dict__` attribute is deprecated, and
698
- may inflict a noticeable performance penalty. When using
699
- :func:`cache_info()`, :attr:`__dict__` already has to be a
700
- mutable mapping.
724
+ provide a mutable :attr:`__dict__` attribute is no longer
725
+ supported, and now raises a :exc:`TypeError`.
726
+
727
+ .. autoclass:: CacheInfo
728
+
729
+ A named tuple with the fields `hits`, `misses`, `maxsize` and
730
+ `currsize`, as returned by the :meth:`cache_info()` function of the
731
+ :func:`cached` and :func:`cachedmethod` decorators. If the
732
+ decorator's `cache` is a :class:`Cache` instance, `maxsize` and
733
+ `currsize` report its :attr:`~Cache.maxsize` and
734
+ :attr:`~Cache.currsize` properties. For any other mutable mapping,
735
+ `maxsize` is :const:`None` and `currsize` is `len(cache)`, so
736
+ custom cache classes used with `info=True` must support
737
+ :func:`len`.
738
+
739
+ .. versionchanged:: 8.0
740
+
741
+ :class:`CacheInfo` is now part of the public API. Cache objects
742
+ that are not :class:`Cache` instances must support :func:`len`
743
+ when used with `info=True`.
701
744
 
702
745
 
703
746
  *****************************************************************
@@ -846,4 +889,5 @@ all the decorators in this module are thread-safe.
846
889
  .. _context manager: https://docs.python.org/3/glossary.html#term-context-manager
847
890
  .. _mapping: https://docs.python.org/3/glossary.html#term-mapping
848
891
  .. _mutable: https://docs.python.org/3/glossary.html#term-mutable
892
+ .. _pickled: https://docs.python.org/3/library/pickle.html
849
893
  .. _thread-safe: https://en.wikipedia.org/wiki/Thread_safety
@@ -15,14 +15,13 @@ maintainers = [
15
15
  readme = "README.rst"
16
16
  license = "MIT"
17
17
  license-files = ["LICENSE"]
18
- requires-python = ">= 3.10"
18
+ requires-python = ">= 3.11"
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.10",
26
25
  "Programming Language :: Python :: 3.11",
27
26
  "Programming Language :: Python :: 3.12",
28
27
  "Programming Language :: Python :: 3.13",
@@ -2,6 +2,7 @@
2
2
 
3
3
  __all__ = (
4
4
  "Cache",
5
+ "CacheInfo",
5
6
  "FIFOCache",
6
7
  "LFUCache",
7
8
  "LRUCache",
@@ -12,7 +13,7 @@ __all__ = (
12
13
  "cachedmethod",
13
14
  )
14
15
 
15
- __version__ = "7.2.8"
16
+ __version__ = "8.0.0"
16
17
 
17
18
  import collections
18
19
  import collections.abc
@@ -212,7 +213,7 @@ class LFUCache(Cache):
212
213
 
213
214
  def __init__(self, count):
214
215
  self.count = count
215
- self.keys = set()
216
+ self.keys = collections.OrderedDict()
216
217
 
217
218
  def unlink(self):
218
219
  next = self.next
@@ -244,13 +245,13 @@ class LFUCache(Cache):
244
245
  link.next = root.next
245
246
  root.next = link.next.prev = link
246
247
  link.prev = root
247
- link.keys.add(key)
248
+ link.keys[key] = None
248
249
  self.__links[key] = link
249
250
 
250
251
  def __delitem__(self, key, cache_delitem=Cache.__delitem__):
251
252
  cache_delitem(self, key)
252
253
  link = self.__links.pop(key)
253
- link.keys.remove(key)
254
+ del link.keys[key]
254
255
  if not link.keys:
255
256
  link.unlink()
256
257
 
@@ -260,7 +261,7 @@ class LFUCache(Cache):
260
261
  curr = root.next
261
262
  if curr is root:
262
263
  raise KeyError("%s is empty" % type(self).__name__) from None
263
- key = next(iter(curr.keys)) # remove an arbitrary element
264
+ key = next(iter(curr.keys)) # remove the "oldest" element
264
265
  value = Cache.__getitem__(self, key)
265
266
  del self[key]
266
267
  return (key, value)
@@ -283,8 +284,8 @@ class LFUCache(Cache):
283
284
  curr.next = link.next
284
285
  link.next = curr.next.prev = curr
285
286
  curr.prev = link
286
- curr.keys.add(key)
287
- link.keys.remove(key)
287
+ curr.keys[key] = None
288
+ del link.keys[key]
288
289
  if not link.keys:
289
290
  link.unlink()
290
291
  self.__links[key] = curr
@@ -740,11 +741,17 @@ class TLRUCache(_TimedCache):
740
741
  cache_delitem(self, key)
741
742
 
742
743
 
743
- # note that the runtime __name__ is "CacheInfo", as in stdlib:
744
- # https://github.com/python/cpython/blob/3.14/Lib/functools.py#L520
745
- _CacheInfo = collections.namedtuple(
746
- "CacheInfo", ["hits", "misses", "maxsize", "currsize"]
747
- )
744
+ class CacheInfo(collections.namedtuple("CacheInfo", "hits misses maxsize currsize")):
745
+ """Named tuple holding cache statistics."""
746
+
747
+ __slots__ = ()
748
+
749
+ @classmethod
750
+ def _make_info(cls, cache, hits, misses):
751
+ if isinstance(cache, Cache):
752
+ return cls(hits, misses, cache.maxsize, cache.currsize)
753
+ else:
754
+ return cls(hits, misses, None, len(cache))
748
755
 
749
756
 
750
757
  def cached(cache, key=keys.hashkey, lock=None, condition=None, info=False):
@@ -754,26 +761,12 @@ def cached(cache, key=keys.hashkey, lock=None, condition=None, info=False):
754
761
  """
755
762
  from ._cached import _wrapper
756
763
 
757
- def decorator(func):
758
- if info:
759
- if isinstance(cache, Cache):
760
-
761
- def make_info(hits, misses):
762
- return _CacheInfo(hits, misses, cache.maxsize, cache.currsize)
763
-
764
- elif isinstance(cache, collections.abc.Mapping):
765
-
766
- def make_info(hits, misses):
767
- return _CacheInfo(hits, misses, None, len(cache))
764
+ if cache is None: # no longer supported since v8.0.0
765
+ raise TypeError("cache must not be None")
768
766
 
769
- else:
770
-
771
- def make_info(hits, misses):
772
- return _CacheInfo(hits, misses, 0, 0)
773
-
774
- return _wrapper(func, cache, key, lock, condition, info=make_info)
775
- else:
776
- return _wrapper(func, cache, key, lock, condition)
767
+ def decorator(func):
768
+ make_info = CacheInfo._make_info if info else None
769
+ return _wrapper(func, cache, key, lock, condition, make_info)
777
770
 
778
771
  return decorator
779
772
 
@@ -786,18 +779,7 @@ def cachedmethod(cache, key=keys.methodkey, lock=None, condition=None, info=Fals
786
779
  from ._cachedmethod import _wrapper
787
780
 
788
781
  def decorator(method):
789
- if info:
790
-
791
- def make_info(cache, hits, misses):
792
- if isinstance(cache, Cache):
793
- return _CacheInfo(hits, misses, cache.maxsize, cache.currsize)
794
- elif isinstance(cache, collections.abc.Mapping):
795
- return _CacheInfo(hits, misses, None, len(cache))
796
- else:
797
- raise TypeError("cache(self) must return a mutable mapping")
798
-
799
- return _wrapper(method, cache, key, lock, condition, info=make_info)
800
- else:
801
- return _wrapper(method, cache, key, lock, condition)
782
+ make_info = CacheInfo._make_info if info else None
783
+ return _wrapper(method, cache, key, lock, condition, make_info)
802
784
 
803
785
  return decorator