cachetools 5.4.0__tar.gz → 5.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. {cachetools-5.4.0 → cachetools-5.5.0}/CHANGELOG.rst +14 -0
  2. {cachetools-5.4.0/src/cachetools.egg-info → cachetools-5.5.0}/PKG-INFO +1 -1
  3. {cachetools-5.4.0 → cachetools-5.5.0}/docs/index.rst +49 -7
  4. {cachetools-5.4.0 → cachetools-5.5.0}/src/cachetools/__init__.py +17 -6
  5. {cachetools-5.4.0 → cachetools-5.5.0/src/cachetools.egg-info}/PKG-INFO +1 -1
  6. {cachetools-5.4.0 → cachetools-5.5.0}/tests/__init__.py +0 -1
  7. {cachetools-5.4.0 → cachetools-5.5.0}/tests/test_cache.py +0 -1
  8. {cachetools-5.4.0 → cachetools-5.5.0}/tests/test_fifo.py +0 -1
  9. {cachetools-5.4.0 → cachetools-5.5.0}/tests/test_keys.py +35 -0
  10. {cachetools-5.4.0 → cachetools-5.5.0}/tests/test_lfu.py +0 -1
  11. {cachetools-5.4.0 → cachetools-5.5.0}/tests/test_lru.py +0 -1
  12. {cachetools-5.4.0 → cachetools-5.5.0}/tests/test_mru.py +0 -2
  13. {cachetools-5.4.0 → cachetools-5.5.0}/tests/test_rr.py +0 -1
  14. {cachetools-5.4.0 → cachetools-5.5.0}/tests/test_tlru.py +8 -5
  15. {cachetools-5.4.0 → cachetools-5.5.0}/tests/test_ttl.py +12 -7
  16. {cachetools-5.4.0 → cachetools-5.5.0}/LICENSE +0 -0
  17. {cachetools-5.4.0 → cachetools-5.5.0}/MANIFEST.in +0 -0
  18. {cachetools-5.4.0 → cachetools-5.5.0}/README.rst +0 -0
  19. {cachetools-5.4.0 → cachetools-5.5.0}/docs/conf.py +0 -0
  20. {cachetools-5.4.0 → cachetools-5.5.0}/pyproject.toml +0 -0
  21. {cachetools-5.4.0 → cachetools-5.5.0}/setup.cfg +0 -0
  22. {cachetools-5.4.0 → cachetools-5.5.0}/setup.py +0 -0
  23. {cachetools-5.4.0 → cachetools-5.5.0}/src/cachetools/func.py +0 -0
  24. {cachetools-5.4.0 → cachetools-5.5.0}/src/cachetools/keys.py +0 -0
  25. {cachetools-5.4.0 → cachetools-5.5.0}/src/cachetools.egg-info/SOURCES.txt +0 -0
  26. {cachetools-5.4.0 → cachetools-5.5.0}/src/cachetools.egg-info/dependency_links.txt +0 -0
  27. {cachetools-5.4.0 → cachetools-5.5.0}/src/cachetools.egg-info/top_level.txt +0 -0
  28. {cachetools-5.4.0 → cachetools-5.5.0}/tests/test_cached.py +0 -0
  29. {cachetools-5.4.0 → cachetools-5.5.0}/tests/test_cachedmethod.py +0 -0
  30. {cachetools-5.4.0 → cachetools-5.5.0}/tests/test_func.py +0 -0
  31. {cachetools-5.4.0 → cachetools-5.5.0}/tox.ini +0 -0
@@ -1,3 +1,17 @@
1
+ v5.5.0 (2024-08-18)
2
+ ===================
3
+
4
+ - ``TTLCache.expire()`` returns iterable of expired ``(key, value)``
5
+ pairs.
6
+
7
+ - ``TLRUCache.expire()`` returns iterable of expired ``(key, value)``
8
+ pairs.
9
+
10
+ - Documentation improvements.
11
+
12
+ - Update CI environment.
13
+
14
+
1
15
  v5.4.0 (2024-07-15)
2
16
  ===================
3
17
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cachetools
3
- Version: 5.4.0
3
+ Version: 5.5.0
4
4
  Summary: Extensible memoizing collections and decorators
5
5
  Home-page: https://github.com/tkem/cachetools/
6
6
  Author: Thomas Kemmer
@@ -26,6 +26,8 @@ method calls.
26
26
  from unittest import mock
27
27
  urllib = mock.MagicMock()
28
28
 
29
+ import time
30
+
29
31
 
30
32
  Cache implementations
31
33
  =====================
@@ -153,6 +155,8 @@ computed when the item is inserted into the cache.
153
155
  items that have expired by the current value returned by
154
156
  :attr:`timer`.
155
157
 
158
+ :returns: An iterable of expired `(key, value)` pairs.
159
+
156
160
  .. autoclass:: TLRUCache(maxsize, ttu, timer=time.monotonic, getsizeof=None)
157
161
  :members: popitem, timer, ttu
158
162
 
@@ -164,18 +168,29 @@ computed when the item is inserted into the cache.
164
168
  value of `timer()`.
165
169
 
166
170
  .. testcode::
167
-
168
- from datetime import datetime, timedelta
169
-
171
+
170
172
  def my_ttu(_key, value, now):
171
- # assume value.ttl contains the item's time-to-live in hours
172
- return now + timedelta(hours=value.ttl)
173
+ # assume value.ttu contains the item's time-to-use in seconds
174
+ # note that the _key argument is ignored in this example
175
+ return now + value.ttu
173
176
 
174
- cache = TLRUCache(maxsize=10, ttu=my_ttu, timer=datetime.now)
177
+ cache = TLRUCache(maxsize=10, ttu=my_ttu)
175
178
 
176
179
  The expression `ttu(key, value, timer())` defines the expiration
177
180
  time of a cache item, and must be comparable against later results
178
- of `timer()`.
181
+ of `timer()`. As with :class:`TTLCache`, a custom `timer` function
182
+ can be supplied, which does not have to return a numeric value.
183
+
184
+ .. testcode::
185
+
186
+ from datetime import datetime, timedelta
187
+
188
+ def datetime_ttu(_key, value, now):
189
+ # assume now to be of type datetime.datetime, and
190
+ # value.hours to contain the item's time-to-use in hours
191
+ return now + timedelta(hours=value.hours)
192
+
193
+ cache = TLRUCache(maxsize=10, ttu=datetime_ttu, timer=datetime.now)
179
194
 
180
195
  Items that expire because they have exceeded their time-to-use will
181
196
  be no longer accessible, and will be removed eventually. If no
@@ -193,6 +208,8 @@ computed when the item is inserted into the cache.
193
208
  items that have expired by the current value returned by
194
209
  :attr:`timer`.
195
210
 
211
+ :returns: An iterable of expired `(key, value)` pairs.
212
+
196
213
 
197
214
  Extending cache classes
198
215
  =======================
@@ -217,6 +234,31 @@ cache, this can be achieved by overriding this method in a subclass:
217
234
  >>> c['c'] = 3
218
235
  Key "a" evicted with value "1"
219
236
 
237
+ With :class:`TTLCache` and :class:`TLRUCache`, items may also be
238
+ removed after they expire. In this case, :meth:`popitem` will *not*
239
+ be called, but :meth:`expire` will be called from the next mutating
240
+ operation and will return an iterable of the expired `(key, value)`
241
+ pairs. By overrding :meth:`expire`, a subclass will be able to track
242
+ expired items:
243
+
244
+ .. doctest::
245
+ :pyversion: >= 3
246
+
247
+ >>> class ExpCache(TTLCache):
248
+ ... def expire(self, time=None):
249
+ ... items = super().expire(time)
250
+ ... print(f"Expired items: {items}")
251
+ ... return items
252
+
253
+ >>> c = ExpCache(maxsize=10, ttl=1.0)
254
+ >>> c['a'] = 1
255
+ Expired items: []
256
+ >>> c['b'] = 2
257
+ Expired items: []
258
+ >>> time.sleep(1.5)
259
+ >>> c['c'] = 3
260
+ Expired items: [('a', 1), ('b', 2)]
261
+
220
262
  Similar to the standard library's :class:`collections.defaultdict`,
221
263
  subclasses of :class:`Cache` may implement a :meth:`__missing__`
222
264
  method which is called by :meth:`Cache.__getitem__` if the requested
@@ -13,7 +13,7 @@ __all__ = (
13
13
  "cachedmethod",
14
14
  )
15
15
 
16
- __version__ = "5.4.0"
16
+ __version__ = "5.5.0"
17
17
 
18
18
  import collections
19
19
  import collections.abc
@@ -26,7 +26,6 @@ from . import keys
26
26
 
27
27
 
28
28
  class _DefaultSize:
29
-
30
29
  __slots__ = ()
31
30
 
32
31
  def __getitem__(self, _):
@@ -378,7 +377,6 @@ class TTLCache(_TimedCache):
378
377
  """LRU Cache implementation with per-item time-to-live (TTL) value."""
379
378
 
380
379
  class _Link:
381
-
382
380
  __slots__ = ("key", "expires", "next", "prev")
383
381
 
384
382
  def __init__(self, key=None, expires=None):
@@ -469,19 +467,26 @@ class TTLCache(_TimedCache):
469
467
  return self.__ttl
470
468
 
471
469
  def expire(self, time=None):
472
- """Remove expired items from the cache."""
470
+ """Remove expired items from the cache and return an iterable of the
471
+ expired `(key, value)` pairs.
472
+
473
+ """
473
474
  if time is None:
474
475
  time = self.timer()
475
476
  root = self.__root
476
477
  curr = root.next
477
478
  links = self.__links
479
+ expired = []
478
480
  cache_delitem = Cache.__delitem__
481
+ cache_getitem = Cache.__getitem__
479
482
  while curr is not root and not (time < curr.expires):
483
+ expired.append((curr.key, cache_getitem(self, curr.key)))
480
484
  cache_delitem(self, curr.key)
481
485
  del links[curr.key]
482
486
  next = curr.next
483
487
  curr.unlink()
484
488
  curr = next
489
+ return expired
485
490
 
486
491
  def popitem(self):
487
492
  """Remove and return the `(key, value)` pair least recently used that
@@ -508,7 +513,6 @@ class TLRUCache(_TimedCache):
508
513
 
509
514
  @functools.total_ordering
510
515
  class _Item:
511
-
512
516
  __slots__ = ("key", "expires", "removed")
513
517
 
514
518
  def __init__(self, key=None, expires=None):
@@ -583,7 +587,10 @@ class TLRUCache(_TimedCache):
583
587
  return self.__ttu
584
588
 
585
589
  def expire(self, time=None):
586
- """Remove expired items from the cache."""
590
+ """Remove expired items from the cache and return an iterable of the
591
+ expired `(key, value)` pairs.
592
+
593
+ """
587
594
  if time is None:
588
595
  time = self.timer()
589
596
  items = self.__items
@@ -592,12 +599,16 @@ class TLRUCache(_TimedCache):
592
599
  if len(order) > len(items) * 2:
593
600
  self.__order = order = [item for item in order if not item.removed]
594
601
  heapq.heapify(order)
602
+ expired = []
595
603
  cache_delitem = Cache.__delitem__
604
+ cache_getitem = Cache.__getitem__
596
605
  while order and (order[0].removed or not (time < order[0].expires)):
597
606
  item = heapq.heappop(order)
598
607
  if not item.removed:
608
+ expired.append((item.key, cache_getitem(self, item.key)))
599
609
  cache_delitem(self, item.key)
600
610
  del items[item.key]
611
+ return expired
601
612
 
602
613
  def popitem(self):
603
614
  """Remove and return the `(key, value)` pair least recently used that
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cachetools
3
- Version: 5.4.0
3
+ Version: 5.5.0
4
4
  Summary: Extensible memoizing collections and decorators
5
5
  Home-page: https://github.com/tkem/cachetools/
6
6
  Author: Thomas Kemmer
@@ -2,7 +2,6 @@ import unittest
2
2
 
3
3
 
4
4
  class CacheTestMixin:
5
-
6
5
  Cache = None
7
6
 
8
7
  def test_defaults(self):
@@ -6,5 +6,4 @@ from . import CacheTestMixin
6
6
 
7
7
 
8
8
  class CacheTest(unittest.TestCase, CacheTestMixin):
9
-
10
9
  Cache = cachetools.Cache
@@ -6,7 +6,6 @@ from . import CacheTestMixin
6
6
 
7
7
 
8
8
  class LRUCacheTest(unittest.TestCase, CacheTestMixin):
9
-
10
9
  Cache = FIFOCache
11
10
 
12
11
  def test_fifo(self):
@@ -21,6 +21,24 @@ class CacheKeysTest(unittest.TestCase):
21
21
  self.assertEqual(key(1, 2, 3), key(1.0, 2.0, 3.0))
22
22
  self.assertEqual(hash(key(1, 2, 3)), hash(key(1.0, 2.0, 3.0)))
23
23
 
24
+ def methodkey(self, key=cachetools.keys.methodkey):
25
+ # similar to hashkey(), but ignores its first positional argument
26
+ self.assertEqual(key("x"), key("y"))
27
+ self.assertEqual(hash(key("x")), hash(key("y")))
28
+ self.assertEqual(key("x", 1, 2, 3), key("y", 1, 2, 3))
29
+ self.assertEqual(hash(key("x", 1, 2, 3)), hash(key("y", 1, 2, 3)))
30
+ self.assertEqual(key("x", 1, 2, 3, x=0), key("y", 1, 2, 3, x=0))
31
+ self.assertEqual(hash(key("x", 1, 2, 3, x=0)), hash(key("y", 1, 2, 3, x=0)))
32
+ self.assertNotEqual(key("x", 1, 2, 3), key("x", 3, 2, 1))
33
+ self.assertNotEqual(key("x", 1, 2, 3), key("x", 1, 2, 3, x=None))
34
+ self.assertNotEqual(key("x", 1, 2, 3, x=0), key("x", 1, 2, 3, x=None))
35
+ self.assertNotEqual(key("x", 1, 2, 3, x=0), key("x", 1, 2, 3, y=0))
36
+ with self.assertRaises(TypeError):
37
+ hash("x", key({}))
38
+ # untyped keys compare equal
39
+ self.assertEqual(key("x", 1, 2, 3), key("y", 1.0, 2.0, 3.0))
40
+ self.assertEqual(hash(key("x", 1, 2, 3)), hash(key("y", 1.0, 2.0, 3.0)))
41
+
24
42
  def test_typedkey(self, key=cachetools.keys.typedkey):
25
43
  self.assertEqual(key(), key())
26
44
  self.assertEqual(hash(key()), hash(key()))
@@ -37,6 +55,23 @@ class CacheKeysTest(unittest.TestCase):
37
55
  # typed keys compare unequal
38
56
  self.assertNotEqual(key(1, 2, 3), key(1.0, 2.0, 3.0))
39
57
 
58
+ def test_typedmethodkey(self, key=cachetools.keys.typedmethodkey):
59
+ # similar to typedkey(), but ignores its first positional argument
60
+ self.assertEqual(key("x"), key("y"))
61
+ self.assertEqual(hash(key("x")), hash(key("y")))
62
+ self.assertEqual(key("x", 1, 2, 3), key("y", 1, 2, 3))
63
+ self.assertEqual(hash(key("x", 1, 2, 3)), hash(key("y", 1, 2, 3)))
64
+ self.assertEqual(key("x", 1, 2, 3, x=0), key("y", 1, 2, 3, x=0))
65
+ self.assertEqual(hash(key("x", 1, 2, 3, x=0)), hash(key("y", 1, 2, 3, x=0)))
66
+ self.assertNotEqual(key("x", 1, 2, 3), key("x", 3, 2, 1))
67
+ self.assertNotEqual(key("x", 1, 2, 3), key("x", 1, 2, 3, x=None))
68
+ self.assertNotEqual(key("x", 1, 2, 3, x=0), key("x", 1, 2, 3, x=None))
69
+ self.assertNotEqual(key("x", 1, 2, 3, x=0), key("x", 1, 2, 3, y=0))
70
+ with self.assertRaises(TypeError):
71
+ hash(key("x", {}))
72
+ # typed keys compare unequal
73
+ self.assertNotEqual(key("x", 1, 2, 3), key("x", 1.0, 2.0, 3.0))
74
+
40
75
  def test_addkeys(self, key=cachetools.keys.hashkey):
41
76
  self.assertIsInstance(key(), tuple)
42
77
  self.assertIsInstance(key(1, 2, 3) + key(4, 5, 6), type(key()))
@@ -6,7 +6,6 @@ from . import CacheTestMixin
6
6
 
7
7
 
8
8
  class LFUCacheTest(unittest.TestCase, CacheTestMixin):
9
-
10
9
  Cache = LFUCache
11
10
 
12
11
  def test_lfu(self):
@@ -6,7 +6,6 @@ from . import CacheTestMixin
6
6
 
7
7
 
8
8
  class LRUCacheTest(unittest.TestCase, CacheTestMixin):
9
-
10
9
  Cache = LRUCache
11
10
 
12
11
  def test_lru(self):
@@ -7,12 +7,10 @@ from . import CacheTestMixin
7
7
 
8
8
 
9
9
  class MRUCacheTest(unittest.TestCase, CacheTestMixin):
10
-
11
10
  # TODO: method to create cache that can be overridden
12
11
  Cache = MRUCache
13
12
 
14
13
  def test_evict__writes_only(self):
15
-
16
14
  with warnings.catch_warnings(record=True) as w:
17
15
  warnings.simplefilter("always")
18
16
  cache = MRUCache(maxsize=2)
@@ -6,7 +6,6 @@ from . import CacheTestMixin
6
6
 
7
7
 
8
8
  class RRCacheTest(unittest.TestCase, CacheTestMixin):
9
-
10
9
  Cache = RRCache
11
10
 
12
11
  def test_rr(self):
@@ -29,7 +29,6 @@ class TLRUTestCache(TLRUCache):
29
29
 
30
30
 
31
31
  class TLRUCacheTest(unittest.TestCase, CacheTestMixin):
32
-
33
32
  Cache = TLRUTestCache
34
33
 
35
34
  def test_ttu(self):
@@ -157,28 +156,32 @@ class TLRUCacheTest(unittest.TestCase, CacheTestMixin):
157
156
  self.assertEqual(2, cache[2])
158
157
  self.assertEqual(3, cache[3])
159
158
 
160
- cache.expire()
159
+ items = cache.expire()
160
+ self.assertEqual(set(), set(items))
161
161
  self.assertEqual({1, 2, 3}, set(cache))
162
162
  self.assertEqual(3, len(cache))
163
163
  self.assertEqual(1, cache[1])
164
164
  self.assertEqual(2, cache[2])
165
165
  self.assertEqual(3, cache[3])
166
166
 
167
- cache.expire(3)
167
+ items = cache.expire(3)
168
+ self.assertEqual({(1, 1)}, set(items))
168
169
  self.assertEqual({2, 3}, set(cache))
169
170
  self.assertEqual(2, len(cache))
170
171
  self.assertNotIn(1, cache)
171
172
  self.assertEqual(2, cache[2])
172
173
  self.assertEqual(3, cache[3])
173
174
 
174
- cache.expire(4)
175
+ items = cache.expire(4)
176
+ self.assertEqual({(2, 2)}, set(items))
175
177
  self.assertEqual({3}, set(cache))
176
178
  self.assertEqual(1, len(cache))
177
179
  self.assertNotIn(1, cache)
178
180
  self.assertNotIn(2, cache)
179
181
  self.assertEqual(3, cache[3])
180
182
 
181
- cache.expire(5)
183
+ items = cache.expire(5)
184
+ self.assertEqual({(3, 3)}, set(items))
182
185
  self.assertEqual(set(), set(cache))
183
186
  self.assertEqual(0, len(cache))
184
187
  self.assertNotIn(1, cache)
@@ -26,7 +26,6 @@ class TTLTestCache(TTLCache):
26
26
 
27
27
 
28
28
  class TTLCacheTest(unittest.TestCase, CacheTestMixin):
29
-
30
29
  Cache = TTLTestCache
31
30
 
32
31
  def test_ttl(self):
@@ -132,28 +131,32 @@ class TTLCacheTest(unittest.TestCase, CacheTestMixin):
132
131
  self.assertEqual(2, cache[2])
133
132
  self.assertEqual(3, cache[3])
134
133
 
135
- cache.expire()
134
+ items = cache.expire()
135
+ self.assertEqual(set(), set(items))
136
136
  self.assertEqual({1, 2, 3}, set(cache))
137
137
  self.assertEqual(3, len(cache))
138
138
  self.assertEqual(1, cache[1])
139
139
  self.assertEqual(2, cache[2])
140
140
  self.assertEqual(3, cache[3])
141
141
 
142
- cache.expire(3)
142
+ items = cache.expire(3)
143
+ self.assertEqual({(1, 1)}, set(items))
143
144
  self.assertEqual({2, 3}, set(cache))
144
145
  self.assertEqual(2, len(cache))
145
146
  self.assertNotIn(1, cache)
146
147
  self.assertEqual(2, cache[2])
147
148
  self.assertEqual(3, cache[3])
148
149
 
149
- cache.expire(4)
150
+ items = cache.expire(4)
151
+ self.assertEqual({(2, 2)}, set(items))
150
152
  self.assertEqual({3}, set(cache))
151
153
  self.assertEqual(1, len(cache))
152
154
  self.assertNotIn(1, cache)
153
155
  self.assertNotIn(2, cache)
154
156
  self.assertEqual(3, cache[3])
155
157
 
156
- cache.expire(5)
158
+ items = cache.expire(5)
159
+ self.assertEqual({(3, 3)}, set(items))
157
160
  self.assertEqual(set(), set(cache))
158
161
  self.assertEqual(0, len(cache))
159
162
  self.assertNotIn(1, cache)
@@ -192,7 +195,9 @@ class TTLCacheTest(unittest.TestCase, CacheTestMixin):
192
195
 
193
196
  cache[1] = 1
194
197
  self.assertEqual(1, len(cache))
195
- cache.expire(datetime.now())
198
+ items = cache.expire(datetime.now())
199
+ self.assertEqual([], list(items))
196
200
  self.assertEqual(1, len(cache))
197
- cache.expire(datetime.now() + timedelta(days=1))
201
+ items = cache.expire(datetime.now() + timedelta(days=1))
202
+ self.assertEqual([(1, 1)], list(items))
198
203
  self.assertEqual(0, len(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