cachetools 7.0.0__tar.gz → 7.0.2__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 (33) hide show
  1. {cachetools-7.0.0 → cachetools-7.0.2}/CHANGELOG.rst +15 -0
  2. {cachetools-7.0.0/src/cachetools.egg-info → cachetools-7.0.2}/PKG-INFO +1 -1
  3. {cachetools-7.0.0 → cachetools-7.0.2}/src/cachetools/__init__.py +51 -6
  4. {cachetools-7.0.0 → cachetools-7.0.2/src/cachetools.egg-info}/PKG-INFO +1 -1
  5. {cachetools-7.0.0 → cachetools-7.0.2}/tests/__init__.py +51 -0
  6. {cachetools-7.0.0 → cachetools-7.0.2}/tests/test_classmethod.py +3 -3
  7. {cachetools-7.0.0 → cachetools-7.0.2}/tests/test_keys.py +1 -1
  8. {cachetools-7.0.0 → cachetools-7.0.2}/tests/test_lfu.py +24 -0
  9. {cachetools-7.0.0 → cachetools-7.0.2}/tests/test_lru.py +21 -0
  10. {cachetools-7.0.0 → cachetools-7.0.2}/tests/test_rr.py +5 -0
  11. {cachetools-7.0.0 → cachetools-7.0.2}/tests/test_tlru.py +61 -3
  12. {cachetools-7.0.0 → cachetools-7.0.2}/tests/test_ttl.py +42 -0
  13. {cachetools-7.0.0 → cachetools-7.0.2}/LICENSE +0 -0
  14. {cachetools-7.0.0 → cachetools-7.0.2}/MANIFEST.in +0 -0
  15. {cachetools-7.0.0 → cachetools-7.0.2}/README.rst +0 -0
  16. {cachetools-7.0.0 → cachetools-7.0.2}/docs/conf.py +0 -0
  17. {cachetools-7.0.0 → cachetools-7.0.2}/docs/index.rst +0 -0
  18. {cachetools-7.0.0 → cachetools-7.0.2}/pyproject.toml +0 -0
  19. {cachetools-7.0.0 → cachetools-7.0.2}/setup.cfg +0 -0
  20. {cachetools-7.0.0 → cachetools-7.0.2}/src/cachetools/_cached.py +0 -0
  21. {cachetools-7.0.0 → cachetools-7.0.2}/src/cachetools/_cachedmethod.py +0 -0
  22. {cachetools-7.0.0 → cachetools-7.0.2}/src/cachetools/func.py +0 -0
  23. {cachetools-7.0.0 → cachetools-7.0.2}/src/cachetools/keys.py +0 -0
  24. {cachetools-7.0.0 → cachetools-7.0.2}/src/cachetools.egg-info/SOURCES.txt +0 -0
  25. {cachetools-7.0.0 → cachetools-7.0.2}/src/cachetools.egg-info/dependency_links.txt +0 -0
  26. {cachetools-7.0.0 → cachetools-7.0.2}/src/cachetools.egg-info/top_level.txt +0 -0
  27. {cachetools-7.0.0 → cachetools-7.0.2}/tests/test_cache.py +0 -0
  28. {cachetools-7.0.0 → cachetools-7.0.2}/tests/test_cached.py +0 -0
  29. {cachetools-7.0.0 → cachetools-7.0.2}/tests/test_cachedmethod.py +0 -0
  30. {cachetools-7.0.0 → cachetools-7.0.2}/tests/test_fifo.py +0 -0
  31. {cachetools-7.0.0 → cachetools-7.0.2}/tests/test_func.py +0 -0
  32. {cachetools-7.0.0 → cachetools-7.0.2}/tests/test_threading.py +0 -0
  33. {cachetools-7.0.0 → cachetools-7.0.2}/tox.ini +0 -0
@@ -1,3 +1,18 @@
1
+ v7.0.2 (2026-03-02)
2
+ ===================
3
+
4
+ - Provide more efficient ``clear()`` implementation for all support
5
+ Cache classes (courtesy Josep Pon Farreny).
6
+
7
+
8
+ v7.0.1 (2026-02-10)
9
+ ===================
10
+
11
+ - Various test improvements.
12
+
13
+ - Update Copilot Instructions.
14
+
15
+
1
16
  v7.0.0 (2026-02-01)
2
17
  ===================
3
18
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cachetools
3
- Version: 7.0.0
3
+ Version: 7.0.2
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>
@@ -12,7 +12,7 @@ __all__ = (
12
12
  "cachedmethod",
13
13
  )
14
14
 
15
- __version__ = "7.0.0"
15
+ __version__ = "7.0.2"
16
16
 
17
17
  import collections
18
18
  import collections.abc
@@ -41,6 +41,9 @@ class _DefaultSize:
41
41
  def pop(self, _key):
42
42
  return 1
43
43
 
44
+ def clear(self):
45
+ pass
46
+
44
47
 
45
48
  class Cache(collections.abc.MutableMapping):
46
49
  """Mutable mapping to serve as a simple cache or cache base class."""
@@ -134,6 +137,17 @@ class Cache(collections.abc.MutableMapping):
134
137
  self[key] = value = default
135
138
  return value
136
139
 
140
+ # Although the MutableMapping.clear() default implementation works
141
+ # perfectly well, it calls popitem() in a loop until the cache is
142
+ # empty, resulting in O(n) complexity. For large caches, this
143
+ # becomes a significant performance bottleneck, so we provide an
144
+ # optimized version for each Cache subclass.
145
+
146
+ def clear(self):
147
+ self.__data.clear()
148
+ self.__size.clear()
149
+ self.__currsize = 0
150
+
137
151
  @property
138
152
  def maxsize(self):
139
153
  """The maximum size of the cache."""
@@ -177,6 +191,10 @@ class FIFOCache(Cache):
177
191
  else:
178
192
  return (key, self.pop(key))
179
193
 
194
+ def clear(self):
195
+ Cache.clear(self)
196
+ self.__order.clear()
197
+
180
198
 
181
199
  class LFUCache(Cache):
182
200
  """Least Frequently Used (LFU) cache implementation."""
@@ -237,6 +255,12 @@ class LFUCache(Cache):
237
255
  key = next(iter(curr.keys)) # remove an arbitrary element
238
256
  return (key, self.pop(key))
239
257
 
258
+ def clear(self):
259
+ Cache.clear(self)
260
+ root = self.__root
261
+ root.prev = root.next = root
262
+ self.__links.clear()
263
+
240
264
  def __touch(self, key):
241
265
  """Increment use count"""
242
266
  link = self.__links[key]
@@ -286,6 +310,10 @@ class LRUCache(Cache):
286
310
  else:
287
311
  return (key, self.pop(key))
288
312
 
313
+ def clear(self):
314
+ Cache.clear(self)
315
+ self.__order.clear()
316
+
289
317
  def __touch(self, key):
290
318
  """Mark as recently used"""
291
319
  try:
@@ -332,6 +360,11 @@ class RRCache(Cache):
332
360
  else:
333
361
  return (key, self.pop(key))
334
362
 
363
+ def clear(self):
364
+ Cache.clear(self)
365
+ self.__index.clear()
366
+ del self.__keys[:]
367
+
335
368
 
336
369
  class _TimedCache(Cache):
337
370
  """Base class for time aware cache implementations."""
@@ -389,11 +422,6 @@ class _TimedCache(Cache):
389
422
  """The timer function used by the cache."""
390
423
  return self.__timer
391
424
 
392
- def clear(self):
393
- with self.__timer as time:
394
- self.expire(time)
395
- Cache.clear(self)
396
-
397
425
  def get(self, *args, **kwargs):
398
426
  with self.__timer:
399
427
  return Cache.get(self, *args, **kwargs)
@@ -406,6 +434,12 @@ class _TimedCache(Cache):
406
434
  with self.__timer:
407
435
  return Cache.setdefault(self, *args, **kwargs)
408
436
 
437
+ def clear(self):
438
+ # Subclasses must override to also reset their own time-tracking
439
+ # structures; we do not call expire() here since clear() should
440
+ # be O(1) regardless of cache contents.
441
+ Cache.clear(self)
442
+
409
443
 
410
444
  class TTLCache(_TimedCache):
411
445
  """LRU Cache implementation with per-item time-to-live (TTL) value."""
@@ -536,6 +570,12 @@ class TTLCache(_TimedCache):
536
570
  else:
537
571
  return (key, self.pop(key))
538
572
 
573
+ def clear(self):
574
+ _TimedCache.clear(self)
575
+ root = self.__root
576
+ root.prev = root.next = root
577
+ self.__links.clear()
578
+
539
579
  def __getlink(self, key):
540
580
  value = self.__links[key]
541
581
  self.__links.move_to_end(key)
@@ -660,6 +700,11 @@ class TLRUCache(_TimedCache):
660
700
  else:
661
701
  return (key, self.pop(key))
662
702
 
703
+ def clear(self):
704
+ _TimedCache.clear(self)
705
+ self.__items.clear()
706
+ del self.__order[:]
707
+
663
708
  def __getitem(self, key):
664
709
  value = self.__items[key]
665
710
  self.__items.move_to_end(key)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cachetools
3
- Version: 7.0.0
3
+ Version: 7.0.2
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>
@@ -266,6 +266,57 @@ class CacheTestMixin:
266
266
 
267
267
  self._test_getsizeof(Cache(maxsize=3))
268
268
 
269
+ def test_clear(self):
270
+ cache = self.Cache(maxsize=2)
271
+ cache.update({1: 1, 2: 2})
272
+
273
+ self.assertEqual(2, len(cache))
274
+ self.assertEqual(2, cache.currsize)
275
+
276
+ cache.clear()
277
+
278
+ self.assertEqual(0, len(cache))
279
+ self.assertEqual(0, cache.currsize)
280
+ self.assertNotIn(1, cache)
281
+ self.assertNotIn(2, cache)
282
+
283
+ # verify cache can be reused after clear
284
+ cache[3] = 3
285
+ cache[4] = 4
286
+ self.assertEqual(2, len(cache))
287
+ self.assertEqual(3, cache[3])
288
+ self.assertEqual(4, cache[4])
289
+
290
+ # verify eviction still works after clear
291
+ cache[5] = 5
292
+ self.assertEqual(2, len(cache))
293
+ self.assertIn(5, cache)
294
+
295
+ def test_clear_empty(self):
296
+ cache = self.Cache(maxsize=2)
297
+ cache.clear() # should not raise
298
+ self.assertEqual(0, len(cache))
299
+ self.assertEqual(0, cache.currsize)
300
+
301
+ def test_clear_getsizeof(self):
302
+ cache = self.Cache(maxsize=10, getsizeof=lambda x: x)
303
+ cache[1] = 1
304
+ cache[2] = 2
305
+ cache[3] = 3
306
+
307
+ self.assertEqual(3, len(cache))
308
+ self.assertEqual(6, cache.currsize)
309
+
310
+ cache.clear()
311
+
312
+ self.assertEqual(0, len(cache))
313
+ self.assertEqual(0, cache.currsize)
314
+
315
+ # verify cache can be reused with getsizeof after clear
316
+ cache[4] = 4
317
+ self.assertEqual(1, len(cache))
318
+ self.assertEqual(4, cache.currsize)
319
+
269
320
  def test_pickle(self):
270
321
  import pickle
271
322
 
@@ -75,7 +75,7 @@ class CachedClassMethodTest(unittest.TestCase):
75
75
  self.assertEqual(Cached.get_typed(0), 1)
76
76
  self.assertEqual(len(w), 2)
77
77
  self.assertIs(w[0].category, DeprecationWarning)
78
- self.assertIs(w[0].category, DeprecationWarning)
78
+ self.assertIs(w[1].category, DeprecationWarning)
79
79
 
80
80
  with warnings.catch_warnings():
81
81
  warnings.simplefilter("ignore")
@@ -99,7 +99,7 @@ class CachedClassMethodTest(unittest.TestCase):
99
99
  self.assertEqual(Cached.get_locked(0), 1)
100
100
  self.assertEqual(len(w), 2)
101
101
  self.assertIs(w[0].category, DeprecationWarning)
102
- self.assertIs(w[0].category, DeprecationWarning)
102
+ self.assertIs(w[1].category, DeprecationWarning)
103
103
 
104
104
  def test_condition(self):
105
105
  Cached.cache = LRUCache(2)
@@ -112,7 +112,7 @@ class CachedClassMethodTest(unittest.TestCase):
112
112
  self.assertEqual(Cached.get_condition(0), 1)
113
113
  self.assertEqual(len(w), 2)
114
114
  self.assertIs(w[0].category, DeprecationWarning)
115
- self.assertIs(w[0].category, DeprecationWarning)
115
+ self.assertIs(w[1].category, DeprecationWarning)
116
116
 
117
117
  def test_clear(self):
118
118
  Cached.cache = LRUCache(2)
@@ -34,7 +34,7 @@ class CacheKeysTest(unittest.TestCase):
34
34
  self.assertNotEqual(key("x", 1, 2, 3, x=0), key("x", 1, 2, 3, x=None))
35
35
  self.assertNotEqual(key("x", 1, 2, 3, x=0), key("x", 1, 2, 3, y=0))
36
36
  with self.assertRaises(TypeError):
37
- hash("x", key({}))
37
+ hash(key("x", {}))
38
38
  # untyped keys compare equal
39
39
  self.assertEqual(key("x", 1, 2, 3), key("y", 1.0, 2.0, 3.0))
40
40
  self.assertEqual(hash(key("x", 1, 2, 3)), hash(key("y", 1.0, 2.0, 3.0)))
@@ -64,3 +64,27 @@ class LFUCacheTest(unittest.TestCase, CacheTestMixin):
64
64
  self.assertEqual(cache[1], "updated")
65
65
  self.assertIn(3, cache)
66
66
  self.assertNotIn(2, cache)
67
+
68
+ def test_lfu_clear(self):
69
+ cache = LFUCache(maxsize=2)
70
+
71
+ cache[1] = 1
72
+ cache[1] # increment frequency
73
+ cache[1] # increment frequency again
74
+ cache[2] = 2
75
+
76
+ cache.clear()
77
+
78
+ self.assertEqual(0, len(cache))
79
+ self.assertEqual(0, cache.currsize)
80
+
81
+ # verify LFU frequency tracking is reset after clear
82
+ cache[3] = 3
83
+ cache[4] = 4
84
+ cache[3] # access 3 to increment frequency
85
+ cache[5] = 5 # should evict 4 (least frequently used)
86
+
87
+ self.assertEqual(2, len(cache))
88
+ self.assertIn(3, cache)
89
+ self.assertIn(5, cache)
90
+ self.assertNotIn(4, cache)
@@ -66,3 +66,24 @@ class LRUCacheTest(unittest.TestCase, CacheTestMixin):
66
66
  self.assertEqual(cache[1], "updated")
67
67
  self.assertIn(3, cache)
68
68
  self.assertNotIn(2, cache)
69
+
70
+ def test_lru_clear(self):
71
+ cache = LRUCache(maxsize=2)
72
+
73
+ cache[1] = 1
74
+ cache[2] = 2
75
+ cache.clear()
76
+
77
+ self.assertEqual(0, len(cache))
78
+ self.assertEqual(0, cache.currsize)
79
+
80
+ # verify LRU order is reset after clear
81
+ cache[3] = 3
82
+ cache[4] = 4
83
+ cache[3] # access 3 to make it most recently used
84
+ cache[5] = 5 # should evict 4 (least recently used)
85
+
86
+ self.assertEqual(2, len(cache))
87
+ self.assertIn(3, cache)
88
+ self.assertIn(5, cache)
89
+ self.assertNotIn(4, cache)
@@ -77,6 +77,11 @@ class RRCacheTest(unittest.TestCase, CacheTestMixin):
77
77
  cache[2] = 2
78
78
  with self.assertRaises(ValueError):
79
79
  cache[3] = 3
80
+ self.assertEqual(len(cache), 2)
81
+ self.assertEqual(cache.currsize, 2)
82
+ self.assertIn(1, cache)
83
+ self.assertIn(2, cache)
84
+ self.assertNotIn(3, cache)
80
85
 
81
86
  def test_rr_default_choice(self):
82
87
  cache = RRCache(maxsize=2)
@@ -6,6 +6,10 @@ from cachetools import TLRUCache
6
6
  from . import CacheTestMixin
7
7
 
8
8
 
9
+ def default_ttu(_key, _value, _time):
10
+ return math.inf
11
+
12
+
9
13
  class Timer:
10
14
  def __init__(self, auto=False):
11
15
  self.auto = auto
@@ -21,9 +25,6 @@ class Timer:
21
25
 
22
26
 
23
27
  class TLRUTestCache(TLRUCache):
24
- def default_ttu(_key, _value, _time):
25
- return math.inf
26
-
27
28
  def __init__(self, maxsize, ttu=default_ttu, **kwargs):
28
29
  TLRUCache.__init__(self, maxsize, ttu, timer=Timer(), **kwargs)
29
30
 
@@ -269,3 +270,60 @@ class TLRUCacheTest(unittest.TestCase, CacheTestMixin):
269
270
  self.assertNotIn(1, cache)
270
271
  self.assertNotIn(2, cache)
271
272
  self.assertNotIn(3, cache)
273
+
274
+ def test_ttu_heap_cleanup(self):
275
+ cache = TLRUCache(maxsize=4, ttu=lambda k, v, t: t + 1, timer=Timer())
276
+ self.assertEqual(0, cache.timer())
277
+
278
+ cache[1] = 1
279
+ cache[2] = 2
280
+
281
+ # replace items to accumulate removed entries in the internal heap
282
+ for i in range(5):
283
+ cache[1] = 10 + i
284
+ cache[2] = 20 + i
285
+
286
+ # this should compact the internal heap
287
+ expired = cache.expire()
288
+ self.assertEqual([], expired)
289
+
290
+ # verify cache is functional after cleanup
291
+ self.assertEqual(2, len(cache))
292
+ self.assertEqual(14, cache[1])
293
+ self.assertEqual(24, cache[2])
294
+ cache[3] = 3
295
+ cache[4] = 4
296
+ self.assertEqual(4, len(cache))
297
+
298
+ cache.timer.tick()
299
+ expired = cache.expire()
300
+ self.assertEqual(4, len(expired))
301
+ self.assertEqual(0, len(cache))
302
+
303
+ def test_tlru_clear(self):
304
+ cache = TLRUCache(maxsize=2, ttu=lambda k, v, t: t + 2, timer=Timer())
305
+
306
+ cache[1] = 1
307
+ cache[2] = 2
308
+ cache.clear()
309
+
310
+ self.assertEqual(0, len(cache))
311
+ self.assertEqual(0, cache.currsize)
312
+
313
+ # verify LRU eviction order is reset after clear
314
+ cache[3] = 3
315
+ cache[4] = 4
316
+ cache[3] # access 3 to make it most recently used
317
+ cache[5] = 5 # should evict 4 (least recently used)
318
+
319
+ self.assertEqual(2, len(cache))
320
+ self.assertIn(3, cache)
321
+ self.assertIn(5, cache)
322
+ self.assertNotIn(4, cache)
323
+
324
+ # verify TLRU expiry still works after clear
325
+ cache[42] = 42
326
+ cache.timer.tick()
327
+ cache.timer.tick()
328
+ cache.timer.tick() # past TTL
329
+ self.assertNotIn(42, cache)
@@ -84,6 +84,20 @@ class TTLCacheTest(unittest.TestCase, CacheTestMixin):
84
84
  self.assertEqual(0, len(cache))
85
85
  self.assertEqual(set(), set(cache))
86
86
 
87
+ def test_ttl_timer(self):
88
+ cache = TTLCache(maxsize=2, ttl=2, timer=Timer())
89
+ self.assertEqual(cache.timer.time, 0)
90
+ self.assertFalse(cache.timer.auto)
91
+
92
+ cache[1] = 1
93
+ cache.timer.tick()
94
+ self.assertEqual(cache.timer.time, 1)
95
+ self.assertEqual(1, cache[1])
96
+
97
+ cache.timer.tick()
98
+ self.assertEqual(cache.timer.time, 2)
99
+ self.assertNotIn(1, cache)
100
+
87
101
  def test_ttl_lru(self):
88
102
  cache = TTLCache(maxsize=2, ttl=1, timer=Timer())
89
103
 
@@ -201,3 +215,31 @@ class TTLCacheTest(unittest.TestCase, CacheTestMixin):
201
215
  items = cache.expire(datetime.now() + timedelta(days=1))
202
216
  self.assertEqual([(1, 1)], list(items))
203
217
  self.assertEqual(0, len(cache))
218
+
219
+ def test_ttl_clear(self):
220
+ cache = TTLCache(maxsize=2, ttl=2, timer=Timer())
221
+
222
+ cache[1] = 1
223
+ cache[2] = 2
224
+ cache.clear()
225
+
226
+ self.assertEqual(0, len(cache))
227
+ self.assertEqual(0, cache.currsize)
228
+
229
+ # verify LRU eviction order is reset after clear
230
+ cache[3] = 3
231
+ cache[4] = 4
232
+ cache[3] # access 3 to make it most recently used
233
+ cache[5] = 5 # should evict 4 (least recently used)
234
+
235
+ self.assertEqual(2, len(cache))
236
+ self.assertIn(3, cache)
237
+ self.assertIn(5, cache)
238
+ self.assertNotIn(4, cache)
239
+
240
+ # verify TTL expiry still works after clear
241
+ cache[42] = 42
242
+ cache.timer.tick()
243
+ cache.timer.tick()
244
+ cache.timer.tick() # past TTL
245
+ self.assertNotIn(42, cache)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes