cachetools 7.3.0__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.3.0 → cachetools-8.0.0}/CHANGELOG.rst +33 -0
- {cachetools-7.3.0/src/cachetools.egg-info → cachetools-8.0.0}/PKG-INFO +12 -9
- {cachetools-7.3.0 → cachetools-8.0.0}/README.rst +10 -6
- {cachetools-7.3.0 → cachetools-8.0.0}/docs/index.rst +66 -29
- {cachetools-7.3.0 → cachetools-8.0.0}/pyproject.toml +1 -2
- {cachetools-7.3.0 → cachetools-8.0.0}/src/cachetools/__init__.py +20 -38
- {cachetools-7.3.0 → cachetools-8.0.0}/src/cachetools/__init__.pyi +20 -23
- 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.3.0 → cachetools-8.0.0}/src/cachetools/_descriptor.py +52 -79
- {cachetools-7.3.0 → cachetools-8.0.0}/src/cachetools/func.pyi +2 -2
- cachetools-8.0.0/src/cachetools/keys.py +61 -0
- {cachetools-7.3.0 → cachetools-8.0.0/src/cachetools.egg-info}/PKG-INFO +12 -9
- {cachetools-7.3.0 → cachetools-8.0.0}/src/cachetools.egg-info/SOURCES.txt +2 -1
- {cachetools-7.3.0 → cachetools-8.0.0}/tests/test_cached.py +148 -31
- {cachetools-7.3.0 → cachetools-8.0.0}/tests/test_cachedmethod.py +121 -65
- {cachetools-7.3.0 → cachetools-8.0.0}/tests/test_keys.py +0 -2
- cachetools-7.3.0/src/cachetools/_cached.py +0 -284
- cachetools-7.3.0/src/cachetools/_cachedmethod.py +0 -344
- cachetools-7.3.0/src/cachetools/keys.py +0 -77
- cachetools-7.3.0/tests/test_classmethod.py +0 -212
- {cachetools-7.3.0 → cachetools-8.0.0}/LICENSE +0 -0
- {cachetools-7.3.0 → cachetools-8.0.0}/MANIFEST.in +0 -0
- {cachetools-7.3.0 → cachetools-8.0.0}/docs/conf.py +0 -0
- {cachetools-7.3.0 → cachetools-8.0.0}/setup.cfg +0 -0
- {cachetools-7.3.0 → cachetools-8.0.0}/src/cachetools/func.py +0 -0
- {cachetools-7.3.0 → cachetools-8.0.0}/src/cachetools/keys.pyi +0 -0
- {cachetools-7.3.0 → cachetools-8.0.0}/src/cachetools/py.typed +0 -0
- {cachetools-7.3.0 → cachetools-8.0.0}/src/cachetools.egg-info/dependency_links.txt +0 -0
- {cachetools-7.3.0 → cachetools-8.0.0}/src/cachetools.egg-info/top_level.txt +0 -0
- {cachetools-7.3.0 → cachetools-8.0.0}/tests/__init__.py +0 -0
- {cachetools-7.3.0 → cachetools-8.0.0}/tests/test_cache.py +0 -0
- {cachetools-7.3.0 → cachetools-8.0.0}/tests/test_fifo.py +0 -0
- {cachetools-7.3.0 → cachetools-8.0.0}/tests/test_func.py +0 -0
- {cachetools-7.3.0 → cachetools-8.0.0}/tests/test_lfu.py +0 -0
- {cachetools-7.3.0 → cachetools-8.0.0}/tests/test_lru.py +0 -0
- {cachetools-7.3.0 → cachetools-8.0.0}/tests/test_rr.py +0 -0
- {cachetools-7.3.0 → cachetools-8.0.0}/tests/test_threading.py +0 -0
- {cachetools-7.3.0 → cachetools-8.0.0}/tests/test_tlru.py +0 -0
- {cachetools-7.3.0 → cachetools-8.0.0}/tests/test_ttl.py +0 -0
- {cachetools-7.3.0 → 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,36 @@
|
|
|
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
|
+
|
|
1
34
|
v7.3.0 (2026-11-05)
|
|
2
35
|
===================
|
|
3
36
|
|
|
@@ -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
|
|
|
@@ -404,9 +414,10 @@ often called with the same arguments:
|
|
|
404
414
|
get_pep.cache_clear()
|
|
405
415
|
|
|
406
416
|
If `info` is set to :const:`True`, the wrapped function is
|
|
407
|
-
instrumented with a :
|
|
408
|
-
|
|
409
|
-
|
|
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.
|
|
410
421
|
|
|
411
422
|
.. note::
|
|
412
423
|
|
|
@@ -540,12 +551,10 @@ often called with the same arguments:
|
|
|
540
551
|
>>> foo(1)
|
|
541
552
|
>>> foo(a=1)
|
|
542
553
|
|
|
543
|
-
..
|
|
554
|
+
.. versionchanged:: 8.0
|
|
544
555
|
|
|
545
|
-
Passing :const:`None` as `cache` parameter is
|
|
546
|
-
|
|
547
|
-
mainly for consistency with :func:`cachedmethod`, which no
|
|
548
|
-
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`.
|
|
549
558
|
|
|
550
559
|
|
|
551
560
|
.. decorator:: cachedmethod(cache, key=cachetools.keys.methodkey, lock=None, condition=None, info=False)
|
|
@@ -631,6 +640,17 @@ often called with the same arguments:
|
|
|
631
640
|
e.g. :func:`cachetools.keys.hashkey`, if you need `self` to be part
|
|
632
641
|
of the cache key.
|
|
633
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
|
+
|
|
634
654
|
When using a shared cache for multiple methods, be aware that
|
|
635
655
|
different cache keys must be created for each method even when
|
|
636
656
|
function arguments are the same, just as with the :func:`cached`
|
|
@@ -674,7 +694,8 @@ often called with the same arguments:
|
|
|
674
694
|
|
|
675
695
|
Note how keyword arguments are used with :func:`functools.partial`
|
|
676
696
|
to create distinct cache keys, to avoid issues with
|
|
677
|
-
:func:`~cachetools.keys.methodkey` skipping its initial `self`
|
|
697
|
+
:func:`~cachetools.keys.methodkey` skipping its initial `self`
|
|
698
|
+
argument.
|
|
678
699
|
|
|
679
700
|
.. versionchanged:: 7.0
|
|
680
701
|
|
|
@@ -688,23 +709,38 @@ often called with the same arguments:
|
|
|
688
709
|
Require :attr:`__dict__` to be a mutable mapping to support
|
|
689
710
|
per-instance :meth:`cache_info()`.
|
|
690
711
|
|
|
691
|
-
Returning :const:`None` from `cache(self)` is no longer
|
|
712
|
+
Returning :const:`None` from `cache(self)` is no longer
|
|
713
|
+
supported.
|
|
692
714
|
|
|
693
|
-
..
|
|
715
|
+
.. versionchanged:: 8.0
|
|
694
716
|
|
|
695
|
-
Using :func:`cachedmethod` with :func:`classmethod` is
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
descriptors
|
|
699
|
-
|
|
700
|
-
using :func:`cache_info()`, an instance method is already
|
|
701
|
-
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.
|
|
702
722
|
|
|
703
723
|
Using :func:`cachedmethod` with an instance that does not
|
|
704
|
-
provide a mutable :attr:`__dict__` attribute is
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
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`.
|
|
708
744
|
|
|
709
745
|
|
|
710
746
|
*****************************************************************
|
|
@@ -853,4 +889,5 @@ all the decorators in this module are thread-safe.
|
|
|
853
889
|
.. _context manager: https://docs.python.org/3/glossary.html#term-context-manager
|
|
854
890
|
.. _mapping: https://docs.python.org/3/glossary.html#term-mapping
|
|
855
891
|
.. _mutable: https://docs.python.org/3/glossary.html#term-mutable
|
|
892
|
+
.. _pickled: https://docs.python.org/3/library/pickle.html
|
|
856
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
|
|
@@ -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
|
|
@@ -17,6 +17,7 @@ from typing import (
|
|
|
17
17
|
|
|
18
18
|
__all__: Final = (
|
|
19
19
|
"Cache",
|
|
20
|
+
"CacheInfo",
|
|
20
21
|
"FIFOCache",
|
|
21
22
|
"LFUCache",
|
|
22
23
|
"LRUCache",
|
|
@@ -138,7 +139,7 @@ class TLRUCache(_TimedCache[_KT, _VT, _TT]):
|
|
|
138
139
|
def ttu(self) -> Callable[[_KT, _VT, _TT], _TT]: ...
|
|
139
140
|
def expire(self, time: _TT | None = None) -> list[tuple[_KT, _VT]]: ...
|
|
140
141
|
|
|
141
|
-
class
|
|
142
|
+
class CacheInfo(NamedTuple):
|
|
142
143
|
hits: int
|
|
143
144
|
misses: int
|
|
144
145
|
maxsize: float | None
|
|
@@ -159,7 +160,7 @@ class _cached_wrapper(Generic[_P, _R]):
|
|
|
159
160
|
__name__: str
|
|
160
161
|
__qualname__: str
|
|
161
162
|
__doc__: str | None
|
|
162
|
-
cache: MutableMapping[Any, Any]
|
|
163
|
+
cache: MutableMapping[Any, Any]
|
|
163
164
|
cache_key: Callable[..., Any] = ...
|
|
164
165
|
cache_lock: AbstractContextManager[Any] | None = None
|
|
165
166
|
cache_condition: _AbstractCondition | None = None
|
|
@@ -198,28 +199,28 @@ class _cached_wrapper_info(_cached_wrapper[_P, _R]):
|
|
|
198
199
|
def __get__( # pyright: ignore[reportIncompatibleMethodOverride]
|
|
199
200
|
self, obj: object, objtype: type | None = None
|
|
200
201
|
) -> _cached_wrapper_info[_P, _R]: ...
|
|
201
|
-
def cache_info(self) ->
|
|
202
|
+
def cache_info(self) -> CacheInfo: ...
|
|
202
203
|
|
|
203
204
|
@overload
|
|
204
205
|
def cached(
|
|
205
|
-
cache: MutableMapping[_KT, Any]
|
|
206
|
+
cache: MutableMapping[_KT, Any],
|
|
206
207
|
key: Callable[..., _KT] = ...,
|
|
207
208
|
lock: AbstractContextManager[Any] | None = None,
|
|
208
209
|
condition: _AbstractCondition | None = None,
|
|
209
|
-
|
|
210
|
-
|
|
210
|
+
*,
|
|
211
|
+
info: Literal[True],
|
|
212
|
+
) -> Callable[[Callable[_P, _R]], _cached_wrapper_info[_P, _R]]: ...
|
|
211
213
|
@overload
|
|
212
214
|
def cached(
|
|
213
|
-
cache: MutableMapping[_KT, Any]
|
|
215
|
+
cache: MutableMapping[_KT, Any],
|
|
214
216
|
key: Callable[..., _KT] = ...,
|
|
215
217
|
lock: AbstractContextManager[Any] | None = None,
|
|
216
218
|
condition: _AbstractCondition | None = None,
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
) -> Callable[[Callable[_P, _R]], _cached_wrapper_info[_P, _R]]: ...
|
|
219
|
+
info: Literal[False] = ...,
|
|
220
|
+
) -> Callable[[Callable[_P, _R]], _cached_wrapper[_P, _R]]: ...
|
|
220
221
|
@overload
|
|
221
222
|
def cached(
|
|
222
|
-
cache: MutableMapping[_KT, Any]
|
|
223
|
+
cache: MutableMapping[_KT, Any],
|
|
223
224
|
key: Callable[..., _KT],
|
|
224
225
|
lock: AbstractContextManager[Any] | None,
|
|
225
226
|
condition: _AbstractCondition | None,
|
|
@@ -240,11 +241,7 @@ class _cachedmethod_unbound(Generic[_P, _R]):
|
|
|
240
241
|
def __get__(
|
|
241
242
|
self, obj: Any, objtype: type | None = None
|
|
242
243
|
) -> _cachedmethod_wrapper[_P, _R]: ...
|
|
243
|
-
@overload
|
|
244
244
|
def __call__(self, obj: Any, /, *args: _P.args, **kwargs: _P.kwargs) -> _R: ...
|
|
245
|
-
# deprecated @classmethod use
|
|
246
|
-
@overload
|
|
247
|
-
def __call__(self, /, *args: _P.args, **kwargs: _P.kwargs) -> _R: ...
|
|
248
245
|
|
|
249
246
|
@type_check_only
|
|
250
247
|
class _cachedmethod_wrapper(Generic[_P, _R]):
|
|
@@ -278,7 +275,7 @@ class _cachedmethod_wrapper_info(_cachedmethod_wrapper[_P, _R]):
|
|
|
278
275
|
def __get__(
|
|
279
276
|
self, obj: Any, objtype: type | None = None
|
|
280
277
|
) -> _cachedmethod_wrapper_info[_P, _R]: ...
|
|
281
|
-
def cache_info(self) ->
|
|
278
|
+
def cache_info(self) -> CacheInfo: ...
|
|
282
279
|
|
|
283
280
|
@overload
|
|
284
281
|
def cachedmethod(
|
|
@@ -286,19 +283,19 @@ def cachedmethod(
|
|
|
286
283
|
key: Callable[..., _KT] = ...,
|
|
287
284
|
lock: Callable[[Any], AbstractContextManager[Any]] | None = None,
|
|
288
285
|
condition: Callable[[Any], _AbstractCondition] | None = None,
|
|
289
|
-
|
|
290
|
-
|
|
286
|
+
*,
|
|
287
|
+
info: Literal[True],
|
|
288
|
+
) -> Callable[
|
|
289
|
+
[Callable[Concatenate[Any, _P], _R]], _cachedmethod_wrapper_info[_P, _R]
|
|
290
|
+
]: ...
|
|
291
291
|
@overload
|
|
292
292
|
def cachedmethod(
|
|
293
293
|
cache: Callable[[Any], MutableMapping[_KT, Any]],
|
|
294
294
|
key: Callable[..., _KT] = ...,
|
|
295
295
|
lock: Callable[[Any], AbstractContextManager[Any]] | None = None,
|
|
296
296
|
condition: Callable[[Any], _AbstractCondition] | None = None,
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
) -> Callable[
|
|
300
|
-
[Callable[Concatenate[Any, _P], _R]], _cachedmethod_wrapper_info[_P, _R]
|
|
301
|
-
]: ...
|
|
297
|
+
info: Literal[False] = ...,
|
|
298
|
+
) -> Callable[[Callable[Concatenate[Any, _P], _R]], _cachedmethod_wrapper[_P, _R]]: ...
|
|
302
299
|
@overload
|
|
303
300
|
def cachedmethod(
|
|
304
301
|
cache: Callable[[Any], MutableMapping[_KT, Any]],
|