cachetools 7.2.4__tar.gz → 7.2.5__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 (38) hide show
  1. {cachetools-7.2.4 → cachetools-7.2.5}/CHANGELOG.rst +31 -15
  2. {cachetools-7.2.4/src/cachetools.egg-info → cachetools-7.2.5}/PKG-INFO +1 -1
  3. {cachetools-7.2.4 → cachetools-7.2.5}/docs/index.rst +91 -87
  4. {cachetools-7.2.4 → cachetools-7.2.5}/src/cachetools/__init__.py +6 -6
  5. {cachetools-7.2.4 → cachetools-7.2.5}/src/cachetools/__init__.pyi +8 -0
  6. {cachetools-7.2.4 → cachetools-7.2.5}/src/cachetools/_cachedmethod.py +22 -14
  7. {cachetools-7.2.4 → cachetools-7.2.5}/src/cachetools/_descriptor.py +47 -6
  8. {cachetools-7.2.4 → cachetools-7.2.5/src/cachetools.egg-info}/PKG-INFO +1 -1
  9. {cachetools-7.2.4 → cachetools-7.2.5}/tests/test_cachedmethod.py +209 -0
  10. {cachetools-7.2.4 → cachetools-7.2.5}/LICENSE +0 -0
  11. {cachetools-7.2.4 → cachetools-7.2.5}/MANIFEST.in +0 -0
  12. {cachetools-7.2.4 → cachetools-7.2.5}/README.rst +0 -0
  13. {cachetools-7.2.4 → cachetools-7.2.5}/docs/conf.py +0 -0
  14. {cachetools-7.2.4 → cachetools-7.2.5}/pyproject.toml +0 -0
  15. {cachetools-7.2.4 → cachetools-7.2.5}/setup.cfg +0 -0
  16. {cachetools-7.2.4 → cachetools-7.2.5}/src/cachetools/_cached.py +0 -0
  17. {cachetools-7.2.4 → cachetools-7.2.5}/src/cachetools/func.py +0 -0
  18. {cachetools-7.2.4 → cachetools-7.2.5}/src/cachetools/func.pyi +0 -0
  19. {cachetools-7.2.4 → cachetools-7.2.5}/src/cachetools/keys.py +0 -0
  20. {cachetools-7.2.4 → cachetools-7.2.5}/src/cachetools/keys.pyi +0 -0
  21. {cachetools-7.2.4 → cachetools-7.2.5}/src/cachetools/py.typed +0 -0
  22. {cachetools-7.2.4 → cachetools-7.2.5}/src/cachetools.egg-info/SOURCES.txt +0 -0
  23. {cachetools-7.2.4 → cachetools-7.2.5}/src/cachetools.egg-info/dependency_links.txt +0 -0
  24. {cachetools-7.2.4 → cachetools-7.2.5}/src/cachetools.egg-info/top_level.txt +0 -0
  25. {cachetools-7.2.4 → cachetools-7.2.5}/tests/__init__.py +0 -0
  26. {cachetools-7.2.4 → cachetools-7.2.5}/tests/test_cache.py +0 -0
  27. {cachetools-7.2.4 → cachetools-7.2.5}/tests/test_cached.py +0 -0
  28. {cachetools-7.2.4 → cachetools-7.2.5}/tests/test_classmethod.py +0 -0
  29. {cachetools-7.2.4 → cachetools-7.2.5}/tests/test_fifo.py +0 -0
  30. {cachetools-7.2.4 → cachetools-7.2.5}/tests/test_func.py +0 -0
  31. {cachetools-7.2.4 → cachetools-7.2.5}/tests/test_keys.py +0 -0
  32. {cachetools-7.2.4 → cachetools-7.2.5}/tests/test_lfu.py +0 -0
  33. {cachetools-7.2.4 → cachetools-7.2.5}/tests/test_lru.py +0 -0
  34. {cachetools-7.2.4 → cachetools-7.2.5}/tests/test_rr.py +0 -0
  35. {cachetools-7.2.4 → cachetools-7.2.5}/tests/test_threading.py +0 -0
  36. {cachetools-7.2.4 → cachetools-7.2.5}/tests/test_tlru.py +0 -0
  37. {cachetools-7.2.4 → cachetools-7.2.5}/tests/test_ttl.py +0 -0
  38. {cachetools-7.2.4 → cachetools-7.2.5}/tox.ini +0 -0
@@ -1,3 +1,19 @@
1
+ v7.2.5 (2026-10-25)
2
+ ===================
3
+
4
+ - Fix ``@cachedmethod`` with ``condition`` for decorated methods
5
+ taking a ``pending`` keyword argument (broken since v7.0.0).
6
+
7
+ - Fix pickling and deep-copying of instances using ``@cachedmethod``
8
+ (broken since v7.0.0).
9
+
10
+ - Fix calling ``@cachedmethod`` decorated methods through the class
11
+ with the receiver (``self``) passed as a keyword argument (broken
12
+ since v7.2.4).
13
+
14
+ - Various documentation improvements.
15
+
16
+
1
17
  v7.2.4 (2026-10-19)
2
18
  ===================
3
19
 
@@ -162,7 +178,7 @@ v7.0.3 (2026-03-05)
162
178
  v7.0.2 (2026-03-02)
163
179
  ===================
164
180
 
165
- - Provide more efficient ``clear()`` implementation for all support
181
+ - Provide more efficient ``clear()`` implementation for all supported
166
182
  Cache classes (courtesy Josep Pon Farreny).
167
183
 
168
184
 
@@ -226,7 +242,7 @@ v6.2.5 (2026-01-25)
226
242
  v6.2.4 (2025-12-15)
227
243
  ===================
228
244
 
229
- - Fix license information displayed on PyPI be using an updated
245
+ - Fix license information displayed on PyPI by using an updated
230
246
  version of ``twine`` for uploading.
231
247
 
232
248
 
@@ -299,7 +315,7 @@ v6.0.0 (2025-05-23)
299
315
  - Convert the ``cachetools.func`` decorators to use a
300
316
  ``threading.Condition`` instance to deal with `cache stampede
301
317
  <https://en.wikipedia.org/wiki/Cache_stampede>`_ issues. Note that
302
- this *may* result in a noticable performance degradation, depending
318
+ this *may* result in a noticeable performance degradation, depending
303
319
  on your actual use case.
304
320
 
305
321
  - Deprecate support for ``cache(self)`` returning ``None`` to suppress
@@ -424,7 +440,7 @@ v5.0.0 (2021-12-21)
424
440
 
425
441
  ``from cachetools.ttl import TTLCache``
426
442
 
427
- will no longer work. Use
443
+ will no longer work. Use
428
444
 
429
445
  ``from cachetools import TTLCache``
430
446
 
@@ -541,7 +557,7 @@ v3.1.1 (2019-05-23)
541
557
 
542
558
  - Document how to use shared caches with ``@cachedmethod``.
543
559
 
544
- - Fix pickling/unpickling of cache keys
560
+ - Fix pickling/unpickling of cache keys.
545
561
 
546
562
 
547
563
  v3.1.0 (2019-01-29)
@@ -670,17 +686,17 @@ v1.1.0 (2015-08-28)
670
686
 
671
687
  - Add ``hashkey`` and ``typedkey`` functions.
672
688
 
673
- - Add `key` and `lock` arguments to ``@cachedmethod``.
689
+ - Add ``key`` and ``lock`` arguments to ``@cachedmethod``.
674
690
 
675
691
  - Set ``__wrapped__`` attributes for Python versions < 3.2.
676
692
 
677
693
  - Move ``functools`` compatible decorators to ``cachetools.func``.
678
694
 
679
- - Deprecate ``@cachedmethod`` `typed` argument.
695
+ - Deprecate ``@cachedmethod`` ``typed`` argument.
680
696
 
681
- - Deprecate `cache` attribute for ``@cachedmethod`` wrappers.
697
+ - Deprecate ``cache`` attribute for ``@cachedmethod`` wrappers.
682
698
 
683
- - Deprecate `getsizeof` and `lock` arguments for `cachetools.func`
699
+ - Deprecate ``getsizeof`` and ``lock`` arguments for ``cachetools.func``
684
700
  decorator.
685
701
 
686
702
 
@@ -739,13 +755,13 @@ v0.8.0 (2014-12-03)
739
755
 
740
756
  - Add ``Cache.__missing__()``.
741
757
 
742
- - Feature freeze for `v1.0`.
758
+ - Feature freeze for ``v1.0``.
743
759
 
744
760
 
745
761
  v0.7.1 (2014-11-22)
746
762
  ===================
747
763
 
748
- - Fix `MANIFEST.in`.
764
+ - Fix ``MANIFEST.in``.
749
765
 
750
766
 
751
767
  v0.7.0 (2014-11-12)
@@ -753,7 +769,7 @@ v0.7.0 (2014-11-12)
753
769
 
754
770
  - Deprecate ``TTLCache.ExpiredError``.
755
771
 
756
- - Add `choice` argument to ``RRCache`` constructor.
772
+ - Add ``choice`` argument to ``RRCache`` constructor.
757
773
 
758
774
  - Refactor ``LFUCache``, ``LRUCache`` and ``TTLCache``.
759
775
 
@@ -768,7 +784,7 @@ v0.6.0 (2014-10-13)
768
784
 
769
785
  - Support unsynchronized function decorators.
770
786
 
771
- - Allow ``@cachedmethod.cache()`` to return None
787
+ - Allow ``@cachedmethod.cache()`` to return ``None``.
772
788
 
773
789
 
774
790
  v0.5.1 (2014-09-25)
@@ -796,7 +812,7 @@ v0.4.0 (2014-06-16)
796
812
 
797
813
  - Add ``Cache`` base class.
798
814
 
799
- - Remove ``@cachedmethod`` `lock` parameter.
815
+ - Remove ``@cachedmethod`` ``lock`` parameter.
800
816
 
801
817
 
802
818
  v0.3.1 (2014-05-07)
@@ -804,7 +820,7 @@ v0.3.1 (2014-05-07)
804
820
 
805
821
  - Add proper locking for ``cache_clear()`` and ``cache_info()``.
806
822
 
807
- - Report `size` in ``cache_info()``.
823
+ - Report ``size`` in ``cache_info()``.
808
824
 
809
825
 
810
826
  v0.3.0 (2014-05-06)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cachetools
3
- Version: 7.2.4
3
+ Version: 7.2.5
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>
@@ -36,21 +36,23 @@ Cache implementations
36
36
  This module provides several classes implementing caches using
37
37
  different cache algorithms. All these classes derive from class
38
38
  :class:`Cache`, which in turn derives from
39
- :class:`collections.abc.MutableMapping`, and provide :attr:`maxsize`
40
- and :attr:`currsize` properties to retrieve the maximum and current
39
+ :class:`collections.abc.MutableMapping`, and provide
40
+ :attr:`~Cache.maxsize` and :attr:`~Cache.currsize` properties to
41
+ retrieve the maximum and current
41
42
  size of the cache. When a cache is full, :meth:`Cache.__setitem__()`
42
43
  calls :meth:`self.popitem()` repeatedly until there is enough room for
43
44
  the item to be added.
44
45
 
45
- In general, a cache's size is the total size of its item's values.
46
- Therefore, :class:`Cache` provides a :meth:`getsizeof` method, which
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
47
48
  returns the size of a given `value`. The default implementation of
48
- :meth:`getsizeof` returns :const:`1` irrespective of its argument,
49
+ :meth:`~Cache.getsizeof` returns :const:`1` irrespective of its argument,
49
50
  making the cache's size equal to the number of its items, or
50
51
  `len(cache)`. For convenience, all cache classes accept an optional
51
52
  named constructor parameter `getsizeof`, which may specify a callable
52
- ``getsizeof(value) -> int`` used to retrieve the size of an item's
53
- value.
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`.
54
56
 
55
57
  The values of a :class:`Cache` are mutable by default, as are e.g. the
56
58
  values of a :class:`dict`. It is the user's responsibility to take
@@ -92,6 +94,9 @@ inserted into the cache.
92
94
  This class counts how often an item is retrieved, and discards the
93
95
  items used least often to make space when necessary.
94
96
 
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.
99
+
95
100
  .. autoclass:: LRUCache(maxsize, getsizeof=None)
96
101
  :members: popitem
97
102
 
@@ -113,8 +118,8 @@ inserted into the cache.
113
118
  :members: popitem, timer, ttl
114
119
 
115
120
  This class associates a time-to-live value with each item. Items
116
- that expire because they have exceeded their time-to-live will be
117
- no longer accessible, and will be removed eventually. If no
121
+ that expire because they have exceeded their time-to-live will no
122
+ longer be accessible, and will be removed eventually. If no
118
123
  expired items are there to remove, the least recently used items
119
124
  will be discarded first to make space when necessary.
120
125
 
@@ -140,14 +145,13 @@ inserted into the cache.
140
145
 
141
146
  .. method:: expire(self, time=None)
142
147
 
143
- Expired items will be removed from a cache only at the next
144
- mutating operation, e.g. :meth:`__setitem__` or
145
- :meth:`__delitem__`, and therefore may still claim memory.
146
- Calling this method removes all items whose time-to-live would
147
- have expired by `time`, so garbage collection is free to reuse
148
- their memory. If `time` is :const:`None`, this removes all
149
- items that have expired by the current value returned by
150
- :attr:`timer`.
148
+ Expired items will be removed from a cache lazily, e.g. when
149
+ :meth:`__setitem__` is next called, and therefore may still
150
+ claim memory. Calling this method removes all items whose
151
+ time-to-live would have expired by `time`, so garbage collection
152
+ is free to reuse their memory. If `time` is :const:`None`, this
153
+ removes all items that have expired by the current value
154
+ returned by :attr:`timer`.
151
155
 
152
156
  :returns: An iterable of expired `(key, value)` pairs.
153
157
 
@@ -187,20 +191,19 @@ inserted into the cache.
187
191
  cache = TLRUCache(maxsize=10, ttu=datetime_ttu, timer=datetime.now)
188
192
 
189
193
  Items that expire because they have exceeded their time-to-use will
190
- be no longer accessible, and will be removed eventually. If no
194
+ no longer be accessible, and will be removed eventually. If no
191
195
  expired items are there to remove, the least recently used items
192
196
  will be discarded first to make space when necessary.
193
197
 
194
198
  .. method:: expire(self, time=None)
195
199
 
196
- Expired items will be removed from a cache only at the next
197
- mutating operation, e.g. :meth:`__setitem__` or
198
- :meth:`__delitem__`, and therefore may still claim memory.
199
- Calling this method removes all items whose time-to-use would
200
- have expired by `time`, so garbage collection is free to reuse
201
- their memory. If `time` is :const:`None`, this removes all
202
- items that have expired by the current value returned by
203
- :attr:`timer`.
200
+ Expired items will be removed from a cache lazily, e.g. when
201
+ :meth:`__setitem__` is next called, and therefore may still
202
+ claim memory. Calling this method removes all items whose
203
+ time-to-use would have expired by `time`, so garbage collection
204
+ is free to reuse their memory. If `time` is :const:`None`, this
205
+ removes all items that have expired by the current value
206
+ returned by :attr:`timer`.
204
207
 
205
208
  :returns: An iterable of expired `(key, value)` pairs.
206
209
 
@@ -208,10 +211,11 @@ inserted into the cache.
208
211
  Extending cache classes
209
212
  =======================
210
213
 
211
- Sometimes it may be desirable to notice when and what cache items are
212
- evicted, i.e. removed from a cache to make room for new items. Since
213
- all cache implementations call :meth:`popitem` to evict items from the
214
- cache, this can be achieved by overriding this method in a subclass:
214
+ Sometimes it may be desirable to be notified when cache items are
215
+ evicted, i.e. removed from a cache to make room for new items, and
216
+ which ones. Since all cache implementations call :meth:`popitem` to
217
+ evict items from the cache, this can be achieved by overriding this
218
+ method in a subclass:
215
219
 
216
220
  .. doctest::
217
221
 
@@ -229,10 +233,10 @@ cache, this can be achieved by overriding this method in a subclass:
229
233
 
230
234
  With :class:`TTLCache` and :class:`TLRUCache`, items may also be
231
235
  removed after they expire. In this case, :meth:`popitem` will *not*
232
- be called, but :meth:`expire` will be called from the next mutating
233
- operation and will return an iterable of the expired `(key, value)`
234
- pairs. By overriding :meth:`expire`, a subclass will be able to track
235
- expired items:
236
+ be called, but :meth:`expire` will be called lazily, e.g. from the
237
+ next :meth:`__setitem__`, and will return an iterable of the expired
238
+ `(key, value)` pairs. By overriding :meth:`expire`, a subclass will
239
+ be able to track expired items:
236
240
 
237
241
  .. doctest::
238
242
 
@@ -329,16 +333,16 @@ often called with the same arguments:
329
333
  therefore must be `thread-safe`_ by itself.
330
334
 
331
335
  If `condition` is not :const:`None`, it must specify a `condition
332
- variable`_, i.e. an object providing :func:`wait()`,
333
- :func:`wait_for()`, :func:`notify()` and :func:`notify_all()`
336
+ variable`_, i.e. an object providing :meth:`wait()`,
337
+ :meth:`wait_for()`, :meth:`notify()` and :meth:`notify_all()`
334
338
  methods as defined by :class:`threading.Condition`. Using a
335
339
  `condition` variable will prevent concurrent execution of the
336
340
  wrapped function with *identical* parameters, or cache keys.
337
341
  Instead, a calling thread will check if an identical function call
338
- is already executing, and will then :func:`wait()` for the pending
339
- call to finish. The executing thread will :func:`notify()` any
340
- waiting threads as soon as the function completes, which will then
341
- return the cached function result.
342
+ is already executing, and will then wait for the pending call to
343
+ finish. The executing thread will notify any waiting threads as
344
+ soon as the function completes, which will then return the cached
345
+ function result.
342
346
 
343
347
  .. note::
344
348
 
@@ -346,7 +350,7 @@ often called with the same arguments:
346
350
  make :func:`cached` `thread-safe`_, it may still be subject to
347
351
  `cache stampede`_ issues under high load, depending on your
348
352
  actual use case. Providing a `condition` variable will mitigate
349
- these situations, but will inflict some performance penalty.
353
+ these situations, but will incur a performance penalty.
350
354
 
351
355
  If no separate `lock` parameter is provided, `condition` must also
352
356
  implement the `context manager`_ protocol, and will also be used to
@@ -374,7 +378,7 @@ often called with the same arguments:
374
378
  get_pep.cache.pop(key, None)
375
379
 
376
380
  For the common use case of clearing or invalidating the cache, the
377
- decorator also provides a :func:`cache_clear()` function which
381
+ decorator also provides a :meth:`cache_clear()` function which
378
382
  takes care of locking automatically, if needed:
379
383
 
380
384
  .. testcode::
@@ -389,8 +393,8 @@ often called with the same arguments:
389
393
 
390
394
  .. note::
391
395
 
392
- Note that this will inflict some performance penalty, so it has
393
- to be enabled explicitly.
396
+ This will incur a small performance penalty, so it has to be
397
+ enabled explicitly.
394
398
 
395
399
  .. doctest::
396
400
 
@@ -427,18 +431,18 @@ often called with the same arguments:
427
431
  >>> # compute Fibonacci numbers
428
432
  >>> @cached(numcache, key=partial(hashkey, 'fib'))
429
433
  ... def fib(n):
430
- ... return n if n < 2 else fib(n - 1) + fib(n - 2)
434
+ ... return n if n < 2 else fib(n - 1) + fib(n - 2)
431
435
 
432
436
  >>> # compute Lucas numbers
433
437
  >>> @cached(numcache, key=partial(hashkey, 'luc'))
434
438
  ... def luc(n):
435
- ... return 2 - n if n < 2 else luc(n - 1) + luc(n - 2)
439
+ ... return 2 - n if n < 2 else luc(n - 1) + luc(n - 2)
436
440
 
437
441
  >>> fib(42)
438
442
  267914296
439
443
  >>> luc(42)
440
444
  599074578
441
- >>> list(sorted(numcache.items()))
445
+ >>> sorted(numcache.items())
442
446
  [..., (('fib', 42), 267914296), ..., (('luc', 42), 599074578)]
443
447
 
444
448
 
@@ -507,7 +511,7 @@ often called with the same arguments:
507
511
  .. doctest::
508
512
 
509
513
  >>> def foo(a=1):
510
- ... _foo(a)
514
+ ... return _foo(a)
511
515
  ...
512
516
  >>> @cached(LRUCache(maxsize=100))
513
517
  ... def _foo(a):
@@ -533,10 +537,10 @@ often called with the same arguments:
533
537
  saves results in a cache.
534
538
 
535
539
  The main difference between this and the :func:`cached` function
536
- decorator is that `cache`, `lock` and `condition` are not passed
537
- objects, but functions. Those will be called with :const:`self` as
538
- their sole argument to retrieve a valid cache, lock, or condition
539
- object for the respective instance.
540
+ decorator is that `cache`, `lock` and `condition` are not objects
541
+ but functions. These will be called with `self` as their sole
542
+ argument to retrieve the cache, lock and condition objects for the
543
+ respective instance.
540
544
 
541
545
  .. note::
542
546
 
@@ -549,15 +553,15 @@ often called with the same arguments:
549
553
  `key`, `lock` and `condition` parameters are also available as
550
554
  :attr:`cache`, :attr:`cache_key`, :attr:`cache_lock` and
551
555
  :attr:`cache_condition` attributes of the wrapped instance method.
552
- A :func:`cache_clear()` function will also be provided, as well as
553
- an optional :func:`cache_info()` function reporting per-instance
556
+ A :meth:`cache_clear()` function will also be provided, as well as
557
+ an optional :meth:`cache_info()` function reporting per-instance
554
558
  cache statistics.
555
559
 
556
560
  Also, mostly for efficiency, this decorator requires that the
557
561
  :attr:`__dict__` attribute on each instance be a mutable mapping.
558
- This means it will not work with some types, such as metaclasses,
559
- and those that specify :attr:`__slots__` without including
560
- :attr:`__dict__` as one of the defined slots.
562
+ This means it will not work with some types, such as those that
563
+ specify :attr:`__slots__` without including :attr:`__dict__` as one
564
+ of the defined slots.
561
565
 
562
566
  One advantage of :func:`cachedmethod` over the :func:`cached`
563
567
  function decorator is that cache properties such as `maxsize` can
@@ -587,7 +591,7 @@ often called with the same arguments:
587
591
  >>> # remove an individual cached PEP from the cache
588
592
  >>> key = peps.get.cache_key(320)
589
593
  >>> with peps.get.cache_lock:
590
- ... del peps.get.cache[key]
594
+ ... del peps.get.cache[key]
591
595
 
592
596
  >>> peps.get.cache_info()
593
597
  CacheInfo(hits=3, misses=8, maxsize=32, currsize=7)
@@ -601,13 +605,12 @@ often called with the same arguments:
601
605
  The `key` function will be called as `key(self, *args, **kwargs)`
602
606
  to retrieve a suitable cache key. Note that the default `key`
603
607
  function, :func:`cachetools.keys.methodkey`, ignores its first
604
- implicit argument, i.e. :const:`self`. This has mostly historical
605
- reasons, but also ensures that :const:`self` does not have to be
606
- hashable.
608
+ implicit argument, i.e. `self`. This is mostly for historical
609
+ reasons, but also ensures that `self` does not have to be hashable.
607
610
 
608
611
  You may provide a different `key` function,
609
- e.g. :func:`cachetools.keys.hashkey`, if you need :const:`self` to
610
- be part of the cache key.
612
+ e.g. :func:`cachetools.keys.hashkey`, if you need `self` to be part
613
+ of the cache key.
611
614
 
612
615
  When using a shared cache for multiple methods, be aware that
613
616
  different cache keys must be created for each method even when
@@ -634,7 +637,7 @@ often called with the same arguments:
634
637
  @cachedmethod(lambda self: self.cache, key=partial(methodkey, method='rfc'))
635
638
  def get_rfc(self, num):
636
639
  """Retrieve text of an IETF Request for Comments"""
637
- url = 'https://tools.ietf.org/rfc/rfc%d.txt' % num
640
+ url = 'https://www.rfc-editor.org/rfc/rfc%d.txt' % num
638
641
  with urllib.request.urlopen(url) as s:
639
642
  return s.read()
640
643
 
@@ -652,7 +655,7 @@ often called with the same arguments:
652
655
 
653
656
  Note how keyword arguments are used with :func:`functools.partial`
654
657
  to create distinct cache keys, to avoid issues with
655
- :func:`methodkey` skipping its initial `self` argument.
658
+ :func:`~cachetools.keys.methodkey` skipping its initial `self` argument.
656
659
 
657
660
  .. versionchanged:: 7.0
658
661
 
@@ -664,7 +667,7 @@ often called with the same arguments:
664
667
  instance methods, and are finally officially supported.
665
668
 
666
669
  Require :attr:`__dict__` to be a mutable mapping to support
667
- per-instance :func:`cache_info()`.
670
+ per-instance :meth:`cache_info()`.
668
671
 
669
672
  Returning :const:`None` from `cache(self)` is no longer supported.
670
673
 
@@ -692,18 +695,19 @@ often called with the same arguments:
692
695
  .. module:: cachetools.keys
693
696
 
694
697
  This module provides several functions that can be used as key
695
- functions with the :func:`cached` and :func:`cachedmethod` decorators:
698
+ functions with the :func:`~cachetools.cached` and
699
+ :func:`~cachetools.cachedmethod` decorators:
696
700
 
697
701
  .. autofunction:: hashkey
698
702
 
699
703
  This function returns a :class:`tuple` instance suitable as a cache
700
- key, provided the positional and keywords arguments are hashable.
704
+ key, provided the positional and keyword arguments are hashable.
701
705
 
702
706
  .. autofunction:: methodkey
703
707
 
704
708
  This function is similar to :func:`hashkey`, but ignores its
705
709
  first positional argument, i.e. `self` when used with the
706
- :func:`cachedmethod` decorator.
710
+ :func:`~cachetools.cachedmethod` decorator.
707
711
 
708
712
  .. autofunction:: typedkey
709
713
 
@@ -715,12 +719,12 @@ functions with the :func:`cached` and :func:`cachedmethod` decorators:
715
719
 
716
720
  This function is similar to :func:`typedkey`, but ignores its
717
721
  first positional argument, i.e. `self` when used with the
718
- :func:`cachedmethod` decorator.
722
+ :func:`~cachetools.cachedmethod` decorator.
719
723
 
720
724
  These functions can also be helpful when implementing custom key
721
725
  functions for handling some non-hashable arguments. For example,
722
726
  calling the following function with a dictionary as its `env` argument
723
- will raise a :class:`TypeError`, since :class:`dict` is not hashable::
727
+ will raise a :exc:`TypeError`, since :class:`dict` is not hashable::
724
728
 
725
729
  @cached(LRUCache(maxsize=128))
726
730
  def foo(x, y, z, env={}):
@@ -735,7 +739,7 @@ specially::
735
739
  key += tuple(sorted(env.items()))
736
740
  return key
737
741
 
738
- The :func:`envkey` function can then be used in decorator declarations
742
+ The ``envkey`` function can then be used in decorator declarations
739
743
  like this::
740
744
 
741
745
  @cached(LRUCache(maxsize=128), key=envkey)
@@ -746,19 +750,19 @@ like this::
746
750
 
747
751
 
748
752
  ****************************************************************************
749
- :mod:`cachetools.func` --- :func:`functools.lru_cache` compatible decorators
753
+ :mod:`cachetools.func` --- :func:`functools.lru_cache`-compatible decorators
750
754
  ****************************************************************************
751
755
 
752
756
  .. module:: cachetools.func
753
757
 
754
- To ease migration from (or to) Python 3's :func:`functools.lru_cache`,
755
- this module provides several memoizing function decorators with a
756
- similar API. All these decorators wrap a function with a memoizing
757
- callable that saves up to the `maxsize` most recent calls, using
758
- different caching strategies. If `maxsize` is set to :const:`None`,
759
- the size limit is disabled and the cache can grow without bound.
760
- For :func:`ttl_cache`, the time-to-live still applies, so cached
761
- results expire after `ttl` even without a size limit.
758
+ To ease migration from (or to) :func:`functools.lru_cache`, this
759
+ module provides several memoizing function decorators with a similar
760
+ API. All these decorators wrap a function with a memoizing callable
761
+ that saves up to `maxsize` results, using different caching
762
+ strategies. If `maxsize` is set to :const:`None`, the size limit is
763
+ disabled and the cache can grow without bound. For :func:`ttl_cache`,
764
+ the time-to-live still applies, so cached results expire after `ttl`
765
+ even without a size limit.
762
766
 
763
767
  If the optional argument `typed` is set to :const:`True`, function
764
768
  arguments of different types will be cached separately. For example,
@@ -774,16 +778,16 @@ leaving the `maxsize` at its default value of 128::
774
778
  sentence = sentence.casefold()
775
779
  return sum(sentence.count(vowel) for vowel in 'aeiou')
776
780
 
777
- The wrapped function is instrumented with a :func:`cache_parameters`
781
+ The wrapped function is instrumented with a :meth:`cache_parameters`
778
782
  function that returns a new :class:`dict` showing the values for
779
783
  `maxsize` and `typed`. This is for information purposes only.
780
784
  Mutating the values has no effect.
781
785
 
782
- The wrapped function is also instrumented with :func:`cache_info` and
783
- :func:`cache_clear` functions to provide information about cache
786
+ The wrapped function is also instrumented with :meth:`cache_info` and
787
+ :meth:`cache_clear` functions to provide information about cache
784
788
  performance and clear the cache. Please see the
785
789
  :func:`functools.lru_cache` documentation for details. Also note that
786
- all the decorators in this module are thread-safe by default.
790
+ all the decorators in this module are thread-safe.
787
791
 
788
792
 
789
793
  .. decorator:: fifo_cache(user_function)
@@ -817,8 +821,8 @@ all the decorators in this module are thread-safe by default.
817
821
  .. decorator:: ttl_cache(user_function)
818
822
  ttl_cache(maxsize=128, ttl=600, timer=time.monotonic, typed=False)
819
823
 
820
- Decorator to wrap a function with a memoizing callable that saves
821
- up to `maxsize` results based on a Least Recently Used (LRU)
824
+ Decorator that wraps a function with a memoizing callable that
825
+ saves up to `maxsize` results based on a Least Recently Used (LRU)
822
826
  algorithm with a per-item time-to-live (TTL) value. By default,
823
827
  the time-to-live is set to 600 seconds and :func:`time.monotonic`
824
828
  is used to retrieve the current time.
@@ -12,7 +12,7 @@ __all__ = (
12
12
  "cachedmethod",
13
13
  )
14
14
 
15
- __version__ = "7.2.4"
15
+ __version__ = "7.2.5"
16
16
 
17
17
  import collections
18
18
  import collections.abc
@@ -266,7 +266,7 @@ class LFUCache(Cache):
266
266
  self.__links.clear()
267
267
 
268
268
  def __touch(self, key):
269
- """Increment use count"""
269
+ """Increment use count."""
270
270
  link = self.__links[key]
271
271
  curr = link.next
272
272
  if curr.count != link.count + 1:
@@ -319,7 +319,7 @@ class LRUCache(Cache):
319
319
  self.__order.clear()
320
320
 
321
321
  def __touch(self, key):
322
- """Mark as recently used"""
322
+ """Mark as recently used."""
323
323
  try:
324
324
  self.__order.move_to_end(key)
325
325
  except KeyError:
@@ -370,7 +370,7 @@ class RRCache(Cache):
370
370
 
371
371
 
372
372
  class _TimedCache(Cache):
373
- """Base class for time aware cache implementations."""
373
+ """Base class for time-aware cache implementations."""
374
374
 
375
375
  class _Timer:
376
376
  def __init__(self, timer):
@@ -590,7 +590,7 @@ class TTLCache(_TimedCache):
590
590
 
591
591
 
592
592
  class TLRUCache(_TimedCache):
593
- """Time aware Least Recently Used (TLRU) cache implementation."""
593
+ """Time-aware Least Recently Used (TLRU) cache implementation."""
594
594
 
595
595
  __HEAP_CLEANUP_FACTOR = 2 # clean up the heap if size > N * len(items)
596
596
 
@@ -668,7 +668,7 @@ class TLRUCache(_TimedCache):
668
668
 
669
669
  @property
670
670
  def ttu(self):
671
- """The local time-to-use function used by the cache."""
671
+ """The time-to-use function used by the cache."""
672
672
  return self.__ttu
673
673
 
674
674
  def expire(self, time=None):
@@ -232,6 +232,14 @@ class _cachedmethod_unbound(Generic[_P, _R]):
232
232
  __qualname__: str
233
233
  __doc__: str | None
234
234
  @overload
235
+ def __get__(
236
+ self, obj: None, objtype: type | None = None
237
+ ) -> _cachedmethod_unbound[_P, _R]: ...
238
+ @overload
239
+ def __get__(
240
+ self, obj: Any, objtype: type | None = None
241
+ ) -> _cachedmethod_wrapper[_P, _R]: ...
242
+ @overload
235
243
  def __call__(self, obj: Any, /, *args: _P.args, **kwargs: _P.kwargs) -> _R: ...
236
244
  # deprecated @classmethod use
237
245
  @overload
@@ -17,16 +17,24 @@ def _none(_):
17
17
  class _WrapperBase:
18
18
  """Wrapper base class providing default implementations for properties."""
19
19
 
20
- def __init__(self, obj, method, cache, key, lock=None, cond=None):
20
+ def __init__(self, obj, descriptor, method, cache, key, lock=None, cond=None):
21
21
  if isinstance(obj, type):
22
22
  _warn_classmethod(stacklevel=5)
23
23
  functools.update_wrapper(self, method)
24
24
  self._obj = obj # protected
25
+ self.__descriptor = descriptor
25
26
  self.__cache = cache
26
27
  self.__key = functools.partial(key, obj)
27
28
  self.__lock = lock if lock is not None else _none
28
29
  self.__cond = cond if cond is not None else _none
29
30
 
31
+ def __reduce__(self):
32
+ # rebuild via the descriptor instead of pickling unpicklable
33
+ # internals, e.g. __wrapped__, lock/cond closures; note that
34
+ # this will also reset hits and misses counts for *Info*
35
+ # wrappers
36
+ return (self.__descriptor.__get__, (self._obj,))
37
+
30
38
  def __call__(self, *args, **kwargs):
31
39
  raise NotImplementedError() # pragma: no cover
32
40
 
@@ -59,8 +67,8 @@ class _WrapperBase:
59
67
  def _condition_info(method, cache, key, lock, cond, info):
60
68
  class Descriptor(_MethodDescriptor):
61
69
  class Wrapper(_WrapperBase):
62
- def __init__(self, obj):
63
- super().__init__(obj, method, cache, key, lock, cond)
70
+ def __init__(self, obj, descriptor):
71
+ super().__init__(obj, descriptor, method, cache, key, lock, cond)
64
72
  self.__hits = self.__misses = 0
65
73
  self.__pending = set()
66
74
 
@@ -107,8 +115,8 @@ def _condition_info(method, cache, key, lock, cond, info):
107
115
  def _locked_info(method, cache, key, lock, info):
108
116
  class Descriptor(_MethodDescriptor):
109
117
  class Wrapper(_WrapperBase):
110
- def __init__(self, obj):
111
- super().__init__(obj, method, cache, key, lock)
118
+ def __init__(self, obj, descriptor):
119
+ super().__init__(obj, descriptor, method, cache, key, lock)
112
120
  self.__hits = self.__misses = 0
113
121
 
114
122
  def __call__(self, *args, **kwargs):
@@ -147,8 +155,8 @@ def _locked_info(method, cache, key, lock, info):
147
155
  def _unlocked_info(method, cache, key, info):
148
156
  class Descriptor(_MethodDescriptor):
149
157
  class Wrapper(_WrapperBase):
150
- def __init__(self, obj):
151
- super().__init__(obj, method, cache, key)
158
+ def __init__(self, obj, descriptor):
159
+ super().__init__(obj, descriptor, method, cache, key)
152
160
  self.__hits = self.__misses = 0
153
161
 
154
162
  def __call__(self, *args, **kwargs):
@@ -181,7 +189,7 @@ def _condition(method, cache, key, lock, cond):
181
189
  # backward-compatible weakref dictionary for Python >= 3.13
182
190
  pending = weakref.WeakKeyDictionary()
183
191
 
184
- def wrapper(self, pending, *args, **kwargs):
192
+ def wrapper(self, pending, /, *args, **kwargs):
185
193
  c = cache(self)
186
194
  k = key(self, *args, **kwargs)
187
195
  with lock(self):
@@ -214,8 +222,8 @@ def _condition(method, cache, key, lock, cond):
214
222
 
215
223
  class Descriptor(_ClassMethodDescriptor):
216
224
  class Wrapper(_WrapperBase):
217
- def __init__(self, obj):
218
- super().__init__(obj, method, cache, key, lock, cond)
225
+ def __init__(self, obj, descriptor):
226
+ super().__init__(obj, descriptor, method, cache, key, lock, cond)
219
227
  self.__pending = set()
220
228
 
221
229
  def __call__(self, *args, **kwargs):
@@ -254,8 +262,8 @@ def _locked(method, cache, key, lock):
254
262
 
255
263
  class Descriptor(_ClassMethodDescriptor):
256
264
  class Wrapper(_WrapperBase):
257
- def __init__(self, obj):
258
- super().__init__(obj, method, cache, key, lock)
265
+ def __init__(self, obj, descriptor):
266
+ super().__init__(obj, descriptor, method, cache, key, lock)
259
267
 
260
268
  def __call__(self, *args, **kwargs):
261
269
  return wrapper(self._obj, *args, **kwargs)
@@ -288,8 +296,8 @@ def _unlocked(method, cache, key):
288
296
 
289
297
  class Descriptor(_ClassMethodDescriptor):
290
298
  class Wrapper(_WrapperBase):
291
- def __init__(self, obj):
292
- super().__init__(obj, method, cache, key)
299
+ def __init__(self, obj, descriptor):
300
+ super().__init__(obj, descriptor, method, cache, key)
293
301
 
294
302
  def __call__(self, *args, **kwargs):
295
303
  return wrapper(self._obj, *args, **kwargs)
@@ -26,12 +26,14 @@ class _MethodDescriptor:
26
26
  """Descriptor class implementing the basic descriptor protocol."""
27
27
 
28
28
  def __init__(self, deprecated=False):
29
+ self.__owner = None
29
30
  self.__attrname = None
30
31
  self.__deprecated = deprecated
31
32
  self.__unbound = None
32
33
 
33
34
  def __set_name__(self, owner, name):
34
35
  if self.__attrname is None:
36
+ self.__owner = owner
35
37
  self.__attrname = name
36
38
  elif name != self.__attrname:
37
39
  raise TypeError(
@@ -45,7 +47,7 @@ class _MethodDescriptor:
45
47
  return self.__get_unbound()
46
48
  elif self.__attrname is not None:
47
49
  # replace descriptor instance with wrapper in instance dict
48
- wrapper = self.Wrapper(obj) # type: ignore
50
+ wrapper = self.Wrapper(obj, self) # type: ignore
49
51
  try:
50
52
  descriptor = next(
51
53
  (
@@ -86,12 +88,21 @@ class _MethodDescriptor:
86
88
  raise TypeError(msg) from None
87
89
  elif self.__deprecated:
88
90
  # deprecated @classmethod, warning already raised elsewhere
89
- wrapper = self.Wrapper(obj) # type: ignore
91
+ wrapper = self.Wrapper(obj, self) # type: ignore
90
92
  else:
91
93
  msg = "Cannot use @cachedmethod instance without calling __set_name__ on it"
92
94
  raise TypeError(msg) from None
93
95
  return wrapper
94
96
 
97
+ def __reduce__(self):
98
+ # pickle and copy by reference, so wrappers and their keys in
99
+ # "__cachetools_cachedmethods" can be pickled and deep-copied
100
+ return (self._lookup, (self.__owner, self.__attrname))
101
+
102
+ @staticmethod
103
+ def _lookup(owner, name):
104
+ return owner.__dict__[name]
105
+
95
106
  def __get_unbound(self):
96
107
  # Created once and shared by all class accesses; objtype is not
97
108
  # used for instance lookups, so it need not be bound here.
@@ -111,16 +122,46 @@ class _Unbound:
111
122
  def __init__(self, descriptor):
112
123
  functools.update_wrapper(self, descriptor.__wrapped__)
113
124
  self.__descriptor = descriptor
125
+ self.__receiver = None # name of the wrapped method's first parameter
126
+
127
+ def __reduce__(self):
128
+ # pickle and copy by reference, as functions are
129
+ return (self.__descriptor.__get__, (None,))
130
+
131
+ # bind like a method when aliased in another class body
132
+ def __get__(self, obj, objtype=None):
133
+ if obj is None:
134
+ return self
135
+ return self.__descriptor.__get__(obj)
114
136
 
115
137
  # Receiver is taken from *args so autospec'd instances accept any call,
116
138
  # as they did before class access returned a callable.
117
139
  def __call__(self, /, *args, **kwargs):
118
- if not args:
119
- msg = "Cannot call @cachedmethod through the class without an instance"
120
- raise TypeError(msg)
121
- obj, *args = args
140
+ if args:
141
+ obj, *args = args
142
+ else:
143
+ obj = self.__pop_receiver(kwargs)
122
144
  return self.__descriptor.__get__(obj)(*args, **kwargs)
123
145
 
146
+ def __pop_receiver(self, kwargs):
147
+ # the receiver may also be passed by keyword, as for a plain function
148
+ if self.__receiver is None:
149
+ import inspect
150
+
151
+ self.__receiver = ""
152
+ try:
153
+ param = next(iter(inspect.signature(self).parameters.values()))
154
+ except (ValueError, StopIteration):
155
+ pass # no signature, or no parameters at all
156
+ else:
157
+ if param.kind is param.POSITIONAL_OR_KEYWORD:
158
+ self.__receiver = param.name
159
+ try:
160
+ return kwargs.pop(self.__receiver)
161
+ except KeyError:
162
+ msg = "Cannot call @cachedmethod through the class without an instance"
163
+ raise TypeError(msg) from None
164
+
124
165
 
125
166
  class _ClassMethodDescriptor(_MethodDescriptor):
126
167
  """Descriptor base class supporting deprecated @classmethod use."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cachetools
3
- Version: 7.2.4
3
+ Version: 7.2.5
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>
@@ -1,5 +1,6 @@
1
1
  import unittest
2
2
  import warnings
3
+ from collections.abc import Callable
3
4
 
4
5
  from cachetools import Cache, cachedmethod, keys
5
6
 
@@ -69,6 +70,18 @@ class Cached:
69
70
  def get_lock_cond_info(self, value):
70
71
  return self.__get(value)
71
72
 
73
+ get_aliased = cachedmethod(lambda self: self.cache)(__get)
74
+
75
+
76
+ class CachedChild(Cached):
77
+ def __init__(self, cache, child_cache):
78
+ super().__init__(cache)
79
+ self.child_cache = child_cache
80
+
81
+ @cachedmethod(lambda self: self.child_cache, info=True)
82
+ def get_info(self, value):
83
+ return super().get_info(value) + 1
84
+
72
85
 
73
86
  class Unhashable(Cached):
74
87
  # https://github.com/tkem/cachetools/issues/107
@@ -421,6 +434,121 @@ class MethodDecoratorTestMixin(SimplifiedTestCaseProtocol):
421
434
  self.assertEqual(len(cache), 0)
422
435
  self.assertEqual(cached.cond.count, 4)
423
436
 
437
+ def test_decorator_pickle(self):
438
+ import pickle
439
+
440
+ cache = self.cache(3)
441
+ cached = Cached(cache)
442
+ self.assertEqual(len(cache), 0)
443
+
444
+ unpickled = pickle.loads(pickle.dumps(cached))
445
+ self.assertEqual(len(unpickled.get_cond.cache), 0)
446
+ self.assertEqual(len(cache), 0)
447
+ self.assertEqual(unpickled.get_cond(0), 0)
448
+ self.assertEqual(len(unpickled.get_cond.cache), 1)
449
+ self.assertEqual(len(cache), 0)
450
+ self.assertEqual(unpickled.get_cond(1), 1)
451
+ self.assertEqual(len(unpickled.get_cond.cache), 2)
452
+ self.assertEqual(len(cache), 0)
453
+
454
+ self.assertEqual(len(cache), 0)
455
+ self.assertEqual(cached.get_cond(0), 0)
456
+ self.assertEqual(len(cache), 1)
457
+
458
+ unpickled = pickle.loads(pickle.dumps(cached))
459
+ self.assertEqual(len(unpickled.get_cond.cache), 1)
460
+ self.assertEqual(len(cache), 1)
461
+ self.assertEqual(unpickled.get_cond(0), 0)
462
+ self.assertEqual(len(unpickled.get_cond.cache), 1)
463
+ self.assertEqual(len(cache), 1)
464
+ self.assertEqual(unpickled.get_cond(1), 1)
465
+ self.assertEqual(len(unpickled.get_cond.cache), 2)
466
+ self.assertEqual(len(cache), 1)
467
+
468
+ def test_decorator_pickle_info(self):
469
+ import pickle
470
+
471
+ cache = self.cache(3)
472
+ cached = Cached(cache)
473
+ maxsize = cache.maxsize if isinstance(cache, Cache) else None
474
+ self.assertEqual(len(cache), 0)
475
+
476
+ # hits and misses will be reset when unpickling - since these
477
+ # are primarily used for debugging/tuning, this is probably OK
478
+ unpickled = pickle.loads(pickle.dumps(cached))
479
+ self.assertEqual(unpickled.get_cond_info.cache_info(), (0, 0, maxsize, 0))
480
+ self.assertEqual(unpickled.get_cond_info(0), 0)
481
+ self.assertEqual(unpickled.get_cond_info.cache_info(), (0, 1, maxsize, 1))
482
+ self.assertEqual(unpickled.get_cond_info(1), 1)
483
+ self.assertEqual(unpickled.get_cond_info.cache_info(), (0, 2, maxsize, 2))
484
+ self.assertEqual(len(cache), 0)
485
+
486
+ self.assertEqual(cached.get_cond_info(0), 0)
487
+ self.assertEqual(len(cache), 1)
488
+
489
+ unpickled = pickle.loads(pickle.dumps(cached))
490
+ self.assertEqual(unpickled.get_cond_info.cache_info(), (0, 0, maxsize, 1))
491
+ self.assertEqual(unpickled.get_cond_info(0), 0)
492
+ self.assertEqual(unpickled.get_cond_info.cache_info(), (1, 0, maxsize, 1))
493
+ self.assertEqual(unpickled.get_cond_info(1), 1)
494
+ self.assertEqual(unpickled.get_cond_info.cache_info(), (1, 1, maxsize, 2))
495
+ self.assertEqual(unpickled.get_cond_info(0), 0)
496
+ self.assertEqual(unpickled.get_cond_info.cache_info(), (2, 1, maxsize, 2))
497
+ self.assertEqual(len(cache), 1)
498
+
499
+ def test_decorator_pickle_super(self):
500
+ import pickle
501
+
502
+ self._test_copy_super(lambda obj: pickle.loads(pickle.dumps(obj)))
503
+
504
+ def test_decorator_deepcopy_super(self):
505
+ import copy
506
+
507
+ self._test_copy_super(copy.deepcopy)
508
+
509
+ def _test_copy_super(self, copyfunc: Callable[[CachedChild], CachedChild]):
510
+ base_cache = self.cache(3)
511
+ maxsize = base_cache.maxsize if isinstance(base_cache, Cache) else None
512
+ cached = CachedChild(base_cache, self.cache(3))
513
+ self.assertEqual(cached.get_info(0), 1)
514
+
515
+ copied = copyfunc(cached)
516
+ child = copied.get_info
517
+ base = super(CachedChild, copied).get_info
518
+ self.assertIsNot(base, child)
519
+ self.assertIs(base, super(CachedChild, copied).get_info)
520
+ self.assertIs(child.cache, copied.child_cache)
521
+ self.assertIs(base.cache, copied.cache)
522
+ self.assertEqual(len(vars(copied)["__cachetools_cachedmethods"]), 1)
523
+ # hits and misses are reset
524
+ self.assertEqual(child.cache_info(), (0, 0, maxsize, 1))
525
+ self.assertEqual(base.cache_info(), (0, 0, maxsize, 1))
526
+
527
+ self.assertEqual(copied.get_info(0), 1)
528
+ self.assertEqual(child.cache_info(), (1, 0, maxsize, 1))
529
+ self.assertEqual(base.cache_info(), (0, 0, maxsize, 1))
530
+ self.assertEqual(copied.get_info(1), 2)
531
+ self.assertEqual(child.cache_info(), (1, 1, maxsize, 2))
532
+ self.assertEqual(base.cache_info(), (0, 1, maxsize, 2))
533
+ self.assertEqual(len(vars(copied)["__cachetools_cachedmethods"]), 1)
534
+ self.assertEqual(len(base_cache), 1)
535
+
536
+ def test_decorator_pickle_aliased(self):
537
+ import pickle
538
+
539
+ cache = self.cache(3)
540
+ cached = Cached(cache)
541
+ self.assertEqual(len(cache), 0)
542
+
543
+ self.assertEqual(cached.get_aliased(0), 0)
544
+ self.assertEqual(len(cache), 1)
545
+
546
+ unpickled = pickle.loads(pickle.dumps(cached))
547
+ self.assertEqual(len(unpickled.get_aliased.cache), 1)
548
+ self.assertEqual(unpickled.get_aliased(0), 0)
549
+ self.assertEqual(len(unpickled.get_aliased.cache), 1)
550
+ self.assertEqual(len(cache), 1)
551
+
424
552
  def test_decorator_slots(self):
425
553
 
426
554
  class Slots:
@@ -706,6 +834,28 @@ class CacheMethodTest(unittest.TestCase, MethodDecoratorTestMixin):
706
834
  self.assertEqual(super(GrandChild, cached).get_typed(0), 1)
707
835
  self.assertEqual(super(Child, cached).get_typed(0.0), 0)
708
836
 
837
+ def test_cond_pending_keyword(self):
838
+ # Up until cachetools v7.2.4, @cachedmethod with condition and
839
+ # info=False raised a TypeError when the wrapped method
840
+ # received a valid "pending" keyword argument.
841
+ for info in (False, True):
842
+ with self.subTest(info=info):
843
+
844
+ class Pending(Cached):
845
+ @cachedmethod(
846
+ lambda obj: obj.cache,
847
+ condition=lambda obj: obj.cond,
848
+ info=info,
849
+ )
850
+ def get_pending(self, pending):
851
+ return self.get(pending)
852
+
853
+ cached = Pending(self.cache(2))
854
+ self.assertEqual(cached.get_pending(pending=0), 0)
855
+ self.assertEqual(cached.get_pending(pending=0), 0)
856
+ cached.get_pending.cache_clear()
857
+ self.assertEqual(cached.get_pending(pending=0), 1)
858
+
709
859
 
710
860
  class DictMethodTest(unittest.TestCase, MethodDecoratorTestMixin):
711
861
  def cache(self, minsize, **_kwargs):
@@ -791,6 +941,41 @@ class UnboundMethodTest(unittest.TestCase):
791
941
  with self.assertRaises(TypeError):
792
942
  Cached.get() # type: ignore
793
943
 
944
+ def test_class_call_keyword_receiver(self):
945
+ cached = Cached(Cache(2))
946
+ self.assertEqual(Cached.get(self=cached, value=1), 0) # type: ignore
947
+ self.assertEqual(Cached.get(cached, value=1), 0)
948
+ self.assertEqual(Cached.get(self=cached, value=2), 1) # type: ignore
949
+ self.assertEqual(cached.count, 2)
950
+ with self.assertRaises(TypeError):
951
+ Cached.get(value=1)
952
+
953
+ class Named:
954
+ def __init__(self, cache):
955
+ self.cache = cache
956
+
957
+ @cachedmethod(lambda this: this.cache)
958
+ def get(this, value):
959
+ return value
960
+
961
+ @cachedmethod(lambda self: self.cache)
962
+ def get_posonly(self, /, value):
963
+ return value
964
+
965
+ @cachedmethod(lambda self: self.cache) # type: ignore
966
+ def get_noargs():
967
+ return 42
968
+
969
+ named = Named(Cache(2))
970
+ self.assertEqual(Named.get(this=named, value=1), 1) # type: ignore
971
+ self.assertEqual(Named.get(named, value=1), 1)
972
+ with self.assertRaises(TypeError):
973
+ Named.get(self=named, value=1) # type: ignore
974
+ with self.assertRaises(TypeError):
975
+ Named.get_posonly(self=named, value=1) # type: ignore
976
+ with self.assertRaises(TypeError):
977
+ Named.get_noargs()
978
+
794
979
  def test_class_access_identity(self):
795
980
  self.assertIs(Cached.get, Cached.get)
796
981
  self.assertIs(Cached.get_info, Cached.get_info)
@@ -806,6 +991,30 @@ class UnboundMethodTest(unittest.TestCase):
806
991
  self.assertEqual(Cached.get.__wrapped__(cached, 1), 1)
807
992
  self.assertEqual(len(cached.cache), 0)
808
993
 
994
+ def test_class_access_pickle(self):
995
+ import pickle
996
+
997
+ for proto in range(pickle.HIGHEST_PROTOCOL + 1):
998
+ with self.subTest(proto=proto):
999
+ self.assertIs(pickle.loads(pickle.dumps(Cached.get, proto)), Cached.get)
1000
+
1001
+ def test_class_access_copy(self):
1002
+ import copy
1003
+
1004
+ self.assertIs(copy.copy(Cached.get), Cached.get)
1005
+ self.assertIs(copy.deepcopy(Cached.get), Cached.get)
1006
+
1007
+ def test_class_access_alias(self):
1008
+ class Alias(Cached):
1009
+ alias = Cached.get
1010
+
1011
+ self.assertIs(Alias.alias, Cached.get)
1012
+ cached = Alias(Cache(2))
1013
+ self.assertEqual(cached.alias(1), 0)
1014
+ self.assertEqual(cached.alias(1), 0)
1015
+ self.assertIs(cached.alias, cached.get)
1016
+ self.assertEqual(len(cached.cache), 1)
1017
+
809
1018
  def test_autospec_no_warnings(self):
810
1019
  import unittest.mock
811
1020
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes