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.
- cachetools-8.0.0/AGENTS.md +99 -0
- {cachetools-7.2.8 → cachetools-8.0.0}/CHANGELOG.rst +45 -0
- {cachetools-7.2.8/src/cachetools.egg-info → cachetools-8.0.0}/PKG-INFO +12 -9
- {cachetools-7.2.8 → cachetools-8.0.0}/README.rst +10 -6
- {cachetools-7.2.8 → cachetools-8.0.0}/docs/conf.py +0 -1
- {cachetools-7.2.8 → cachetools-8.0.0}/docs/index.rst +76 -32
- {cachetools-7.2.8 → cachetools-8.0.0}/pyproject.toml +1 -2
- {cachetools-7.2.8 → cachetools-8.0.0}/src/cachetools/__init__.py +26 -44
- {cachetools-7.2.8 → cachetools-8.0.0}/src/cachetools/__init__.pyi +27 -27
- cachetools-8.0.0/src/cachetools/_cached.py +104 -0
- cachetools-8.0.0/src/cachetools/_cachedmethod.py +139 -0
- cachetools-8.0.0/src/cachetools/_decorator.py +216 -0
- {cachetools-7.2.8 → cachetools-8.0.0}/src/cachetools/_descriptor.py +52 -79
- {cachetools-7.2.8 → cachetools-8.0.0}/src/cachetools/func.pyi +2 -2
- cachetools-8.0.0/src/cachetools/keys.py +61 -0
- {cachetools-7.2.8 → cachetools-8.0.0/src/cachetools.egg-info}/PKG-INFO +12 -9
- {cachetools-7.2.8 → cachetools-8.0.0}/src/cachetools.egg-info/SOURCES.txt +2 -1
- {cachetools-7.2.8 → cachetools-8.0.0}/tests/test_cached.py +163 -31
- {cachetools-7.2.8 → cachetools-8.0.0}/tests/test_cachedmethod.py +129 -65
- {cachetools-7.2.8 → cachetools-8.0.0}/tests/test_func.py +65 -0
- {cachetools-7.2.8 → cachetools-8.0.0}/tests/test_keys.py +0 -2
- {cachetools-7.2.8 → cachetools-8.0.0}/tests/test_lfu.py +41 -13
- {cachetools-7.2.8 → cachetools-8.0.0}/tests/test_tlru.py +2 -2
- {cachetools-7.2.8 → cachetools-8.0.0}/tests/test_ttl.py +2 -2
- cachetools-7.2.8/src/cachetools/_cached.py +0 -284
- cachetools-7.2.8/src/cachetools/_cachedmethod.py +0 -344
- cachetools-7.2.8/src/cachetools/keys.py +0 -77
- cachetools-7.2.8/tests/test_classmethod.py +0 -194
- {cachetools-7.2.8 → cachetools-8.0.0}/LICENSE +0 -0
- {cachetools-7.2.8 → cachetools-8.0.0}/MANIFEST.in +0 -0
- {cachetools-7.2.8 → cachetools-8.0.0}/setup.cfg +0 -0
- {cachetools-7.2.8 → cachetools-8.0.0}/src/cachetools/func.py +0 -0
- {cachetools-7.2.8 → cachetools-8.0.0}/src/cachetools/keys.pyi +0 -0
- {cachetools-7.2.8 → cachetools-8.0.0}/src/cachetools/py.typed +0 -0
- {cachetools-7.2.8 → cachetools-8.0.0}/src/cachetools.egg-info/dependency_links.txt +0 -0
- {cachetools-7.2.8 → cachetools-8.0.0}/src/cachetools.egg-info/top_level.txt +0 -0
- {cachetools-7.2.8 → cachetools-8.0.0}/tests/__init__.py +0 -0
- {cachetools-7.2.8 → cachetools-8.0.0}/tests/test_cache.py +0 -0
- {cachetools-7.2.8 → cachetools-8.0.0}/tests/test_fifo.py +0 -0
- {cachetools-7.2.8 → cachetools-8.0.0}/tests/test_lru.py +0 -0
- {cachetools-7.2.8 → cachetools-8.0.0}/tests/test_rr.py +0 -0
- {cachetools-7.2.8 → cachetools-8.0.0}/tests/test_threading.py +0 -0
- {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:
|
|
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.
|
|
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
|
-
|
|
71
|
-
|
|
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).
|
|
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/
|
|
128
|
-
.. _mapping: https://docs.python.org/
|
|
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
|
-
|
|
41
|
-
|
|
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).
|
|
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/
|
|
98
|
-
.. _mapping: https://docs.python.org/
|
|
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/
|
|
@@ -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,
|
|
48
|
-
returns the size of a given `value`. The default implementation
|
|
49
|
-
:meth:`~Cache.getsizeof` returns :const:`1` irrespective of its
|
|
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
|
-
|
|
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
|
-
|
|
98
|
-
|
|
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 :
|
|
401
|
-
|
|
402
|
-
|
|
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
|
-
..
|
|
554
|
+
.. versionchanged:: 8.0
|
|
537
555
|
|
|
538
|
-
Passing :const:`None` as `cache` parameter is
|
|
539
|
-
|
|
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`
|
|
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
|
|
712
|
+
Returning :const:`None` from `cache(self)` is no longer
|
|
713
|
+
supported.
|
|
685
714
|
|
|
686
|
-
..
|
|
715
|
+
.. versionchanged:: 8.0
|
|
687
716
|
|
|
688
|
-
Using :func:`cachedmethod` with :func:`classmethod` is
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
descriptors
|
|
692
|
-
|
|
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
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
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.
|
|
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__ = "
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
287
|
-
link.keys
|
|
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
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
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
|
-
|
|
758
|
-
|
|
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
|
-
|
|
770
|
-
|
|
771
|
-
|
|
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
|