cachetools 6.0.0b4__tar.gz → 6.2.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 (33) hide show
  1. {cachetools-6.0.0b4 → cachetools-6.2.0}/CHANGELOG.rst +21 -1
  2. {cachetools-6.0.0b4/src/cachetools.egg-info → cachetools-6.2.0}/PKG-INFO +11 -8
  3. {cachetools-6.0.0b4 → cachetools-6.2.0}/README.rst +10 -7
  4. {cachetools-6.0.0b4 → cachetools-6.2.0}/src/cachetools/__init__.py +74 -11
  5. {cachetools-6.0.0b4 → cachetools-6.2.0/src/cachetools.egg-info}/PKG-INFO +11 -8
  6. {cachetools-6.0.0b4 → cachetools-6.2.0}/tests/test_cachedmethod.py +128 -77
  7. {cachetools-6.0.0b4 → cachetools-6.2.0}/tests/test_fifo.py +13 -1
  8. {cachetools-6.0.0b4 → cachetools-6.2.0}/tests/test_func.py +5 -0
  9. {cachetools-6.0.0b4 → cachetools-6.2.0}/tests/test_lfu.py +17 -0
  10. {cachetools-6.0.0b4 → cachetools-6.2.0}/tests/test_lru.py +12 -0
  11. cachetools-6.2.0/tests/test_rr.py +83 -0
  12. cachetools-6.0.0b4/tests/test_rr.py +0 -34
  13. {cachetools-6.0.0b4 → cachetools-6.2.0}/LICENSE +0 -0
  14. {cachetools-6.0.0b4 → cachetools-6.2.0}/MANIFEST.in +0 -0
  15. {cachetools-6.0.0b4 → cachetools-6.2.0}/docs/conf.py +0 -0
  16. {cachetools-6.0.0b4 → cachetools-6.2.0}/docs/index.rst +0 -0
  17. {cachetools-6.0.0b4 → cachetools-6.2.0}/pyproject.toml +0 -0
  18. {cachetools-6.0.0b4 → cachetools-6.2.0}/setup.cfg +0 -0
  19. {cachetools-6.0.0b4 → cachetools-6.2.0}/setup.py +0 -0
  20. {cachetools-6.0.0b4 → cachetools-6.2.0}/src/cachetools/_cached.py +0 -0
  21. {cachetools-6.0.0b4 → cachetools-6.2.0}/src/cachetools/_cachedmethod.py +0 -0
  22. {cachetools-6.0.0b4 → cachetools-6.2.0}/src/cachetools/func.py +0 -0
  23. {cachetools-6.0.0b4 → cachetools-6.2.0}/src/cachetools/keys.py +0 -0
  24. {cachetools-6.0.0b4 → cachetools-6.2.0}/src/cachetools.egg-info/SOURCES.txt +0 -0
  25. {cachetools-6.0.0b4 → cachetools-6.2.0}/src/cachetools.egg-info/dependency_links.txt +0 -0
  26. {cachetools-6.0.0b4 → cachetools-6.2.0}/src/cachetools.egg-info/top_level.txt +0 -0
  27. {cachetools-6.0.0b4 → cachetools-6.2.0}/tests/__init__.py +0 -0
  28. {cachetools-6.0.0b4 → cachetools-6.2.0}/tests/test_cache.py +0 -0
  29. {cachetools-6.0.0b4 → cachetools-6.2.0}/tests/test_cached.py +0 -0
  30. {cachetools-6.0.0b4 → cachetools-6.2.0}/tests/test_keys.py +0 -0
  31. {cachetools-6.0.0b4 → cachetools-6.2.0}/tests/test_tlru.py +0 -0
  32. {cachetools-6.0.0b4 → cachetools-6.2.0}/tests/test_ttl.py +0 -0
  33. {cachetools-6.0.0b4 → cachetools-6.2.0}/tox.ini +0 -0
@@ -1,4 +1,24 @@
1
- v6.0.0 (UNRELEASED)
1
+ v6.2.0 (2025-08-25)
2
+ ===================
3
+
4
+ - Improve general ``RRCache`` performance by storing cache keys in an
5
+ additional sequence container. Note that this will increase memory
6
+ consumption.
7
+
8
+ - Add more unit tests.
9
+
10
+
11
+ v6.1.0 (2025-06-16)
12
+ ===================
13
+
14
+ - Improve ``LFUCache`` insertion performance by switching to an
15
+ implementation based on the `cacheing
16
+ <https://pypi.org/project/cacheing/>`_ library.
17
+
18
+ - Update CI environment.
19
+
20
+
21
+ v6.0.0 (2025-05-23)
2
22
  ===================
3
23
 
4
24
  - Require Python 3.9 or later (breaking change).
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cachetools
3
- Version: 6.0.0b4
3
+ Version: 6.2.0
4
4
  Summary: Extensible memoizing collections and decorators
5
5
  Home-page: https://github.com/tkem/cachetools/
6
6
  Author: Thomas Kemmer
@@ -115,11 +115,12 @@ Project Resources
115
115
  Related Projects
116
116
  ------------------------------------------------------------------------
117
117
 
118
- - asyncache_: Helpers to use cachetools with async functions
119
- - cacheing_: Pure Python Cacheing Library
120
- - CacheToolsUtils_: Cachetools Utilities
121
- - kids.cache_: Kids caching library
122
- - shelved-cache_: Persistent cache for Python cachetools
118
+ - asyncache_: Helpers to use cachetools_ with asyncio.
119
+ - cacheing_: Pure Python Cacheing Library.
120
+ - CacheToolsUtils_: Stackable cache classes for sharing, encryption,
121
+ statistics *and more* on top of cachetools_, redis_ and memcached_.
122
+ - shelved-cache_: Persistent cache implementation for Python
123
+ cachetools_.
123
124
 
124
125
 
125
126
  License
@@ -144,7 +145,9 @@ Licensed under the `MIT License`_.
144
145
  .. _MIT License: https://raw.github.com/tkem/cachetools/master/LICENSE
145
146
 
146
147
  .. _asyncache: https://pypi.org/project/asyncache/
147
- .. _cacheing: https://github.com/breid48/cacheing
148
+ .. _cacheing: https://pypi.org/project/cacheing/
148
149
  .. _CacheToolsUtils: https://pypi.org/project/CacheToolsUtils/
149
- .. _kids.cache: https://pypi.org/project/kids.cache/
150
150
  .. _shelved-cache: https://pypi.org/project/shelved-cache/
151
+ .. _cachetools: https://pypi.org/project/cachetools/
152
+ .. _redis: https://redis.io/
153
+ .. _memcached: https://memcached.org/
@@ -90,11 +90,12 @@ Project Resources
90
90
  Related Projects
91
91
  ------------------------------------------------------------------------
92
92
 
93
- - asyncache_: Helpers to use cachetools with async functions
94
- - cacheing_: Pure Python Cacheing Library
95
- - CacheToolsUtils_: Cachetools Utilities
96
- - kids.cache_: Kids caching library
97
- - shelved-cache_: Persistent cache for Python cachetools
93
+ - asyncache_: Helpers to use cachetools_ with asyncio.
94
+ - cacheing_: Pure Python Cacheing Library.
95
+ - CacheToolsUtils_: Stackable cache classes for sharing, encryption,
96
+ statistics *and more* on top of cachetools_, redis_ and memcached_.
97
+ - shelved-cache_: Persistent cache implementation for Python
98
+ cachetools_.
98
99
 
99
100
 
100
101
  License
@@ -119,7 +120,9 @@ Licensed under the `MIT License`_.
119
120
  .. _MIT License: https://raw.github.com/tkem/cachetools/master/LICENSE
120
121
 
121
122
  .. _asyncache: https://pypi.org/project/asyncache/
122
- .. _cacheing: https://github.com/breid48/cacheing
123
+ .. _cacheing: https://pypi.org/project/cacheing/
123
124
  .. _CacheToolsUtils: https://pypi.org/project/CacheToolsUtils/
124
- .. _kids.cache: https://pypi.org/project/kids.cache/
125
125
  .. _shelved-cache: https://pypi.org/project/shelved-cache/
126
+ .. _cachetools: https://pypi.org/project/cachetools/
127
+ .. _redis: https://redis.io/
128
+ .. _memcached: https://memcached.org/
@@ -12,7 +12,7 @@ __all__ = (
12
12
  "cachedmethod",
13
13
  )
14
14
 
15
- __version__ = "6.0.0b4"
15
+ __version__ = "6.2.0"
16
16
 
17
17
  import collections
18
18
  import collections.abc
@@ -170,32 +170,78 @@ class FIFOCache(Cache):
170
170
  class LFUCache(Cache):
171
171
  """Least Frequently Used (LFU) cache implementation."""
172
172
 
173
+ class _Link:
174
+ __slots__ = ("count", "keys", "next", "prev")
175
+
176
+ def __init__(self, count):
177
+ self.count = count
178
+ self.keys = set()
179
+
180
+ def unlink(self):
181
+ next = self.next
182
+ prev = self.prev
183
+ prev.next = next
184
+ next.prev = prev
185
+
173
186
  def __init__(self, maxsize, getsizeof=None):
174
187
  Cache.__init__(self, maxsize, getsizeof)
175
- self.__counter = collections.Counter()
188
+ self.__root = root = LFUCache._Link(0) # sentinel
189
+ root.prev = root.next = root
190
+ self.__links = {}
176
191
 
177
192
  def __getitem__(self, key, cache_getitem=Cache.__getitem__):
178
193
  value = cache_getitem(self, key)
179
194
  if key in self: # __missing__ may not store item
180
- self.__counter[key] -= 1
195
+ self.__touch(key)
181
196
  return value
182
197
 
183
198
  def __setitem__(self, key, value, cache_setitem=Cache.__setitem__):
184
199
  cache_setitem(self, key, value)
185
- self.__counter[key] -= 1
200
+ if key in self.__links:
201
+ return self.__touch(key)
202
+ root = self.__root
203
+ link = root.next
204
+ if link.count != 1:
205
+ link = LFUCache._Link(1)
206
+ link.next = root.next
207
+ root.next = link.next.prev = link
208
+ link.prev = root
209
+ link.keys.add(key)
210
+ self.__links[key] = link
186
211
 
187
212
  def __delitem__(self, key, cache_delitem=Cache.__delitem__):
188
213
  cache_delitem(self, key)
189
- del self.__counter[key]
214
+ link = self.__links.pop(key)
215
+ link.keys.remove(key)
216
+ if not link.keys:
217
+ link.unlink()
190
218
 
191
219
  def popitem(self):
192
220
  """Remove and return the `(key, value)` pair least frequently used."""
193
- try:
194
- ((key, _),) = self.__counter.most_common(1)
195
- except ValueError:
221
+ root = self.__root
222
+ curr = root.next
223
+ if curr is root:
196
224
  raise KeyError("%s is empty" % type(self).__name__) from None
197
- else:
198
- return (key, self.pop(key))
225
+ key = next(iter(curr.keys)) # remove an arbitrary element
226
+ return (key, self.pop(key))
227
+
228
+ def __touch(self, key):
229
+ """Increment use count"""
230
+ link = self.__links[key]
231
+ curr = link.next
232
+ if curr.count != link.count + 1:
233
+ if len(link.keys) == 1:
234
+ link.count += 1
235
+ return
236
+ curr = LFUCache._Link(link.count + 1)
237
+ curr.next = link.next
238
+ link.next = curr.next.prev = curr
239
+ curr.prev = link
240
+ curr.keys.add(key)
241
+ link.keys.remove(key)
242
+ if not link.keys:
243
+ link.unlink()
244
+ self.__links[key] = curr
199
245
 
200
246
 
201
247
  class LRUCache(Cache):
@@ -242,16 +288,33 @@ class RRCache(Cache):
242
288
  def __init__(self, maxsize, choice=random.choice, getsizeof=None):
243
289
  Cache.__init__(self, maxsize, getsizeof)
244
290
  self.__choice = choice
291
+ self.__index = {}
292
+ self.__keys = []
245
293
 
246
294
  @property
247
295
  def choice(self):
248
296
  """The `choice` function used by the cache."""
249
297
  return self.__choice
250
298
 
299
+ def __setitem__(self, key, value, cache_setitem=Cache.__setitem__):
300
+ cache_setitem(self, key, value)
301
+ if key not in self.__index:
302
+ self.__index[key] = len(self.__keys)
303
+ self.__keys.append(key)
304
+
305
+ def __delitem__(self, key, cache_delitem=Cache.__delitem__):
306
+ cache_delitem(self, key)
307
+ index = self.__index.pop(key)
308
+ if index != len(self.__keys) - 1:
309
+ last = self.__keys[-1]
310
+ self.__keys[index] = last
311
+ self.__index[last] = index
312
+ self.__keys.pop()
313
+
251
314
  def popitem(self):
252
315
  """Remove and return a random `(key, value)` pair."""
253
316
  try:
254
- key = self.__choice(list(self))
317
+ key = self.__choice(self.__keys)
255
318
  except IndexError:
256
319
  raise KeyError("%s is empty" % type(self).__name__) from None
257
320
  else:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cachetools
3
- Version: 6.0.0b4
3
+ Version: 6.2.0
4
4
  Summary: Extensible memoizing collections and decorators
5
5
  Home-page: https://github.com/tkem/cachetools/
6
6
  Author: Thomas Kemmer
@@ -115,11 +115,12 @@ Project Resources
115
115
  Related Projects
116
116
  ------------------------------------------------------------------------
117
117
 
118
- - asyncache_: Helpers to use cachetools with async functions
119
- - cacheing_: Pure Python Cacheing Library
120
- - CacheToolsUtils_: Cachetools Utilities
121
- - kids.cache_: Kids caching library
122
- - shelved-cache_: Persistent cache for Python cachetools
118
+ - asyncache_: Helpers to use cachetools_ with asyncio.
119
+ - cacheing_: Pure Python Cacheing Library.
120
+ - CacheToolsUtils_: Stackable cache classes for sharing, encryption,
121
+ statistics *and more* on top of cachetools_, redis_ and memcached_.
122
+ - shelved-cache_: Persistent cache implementation for Python
123
+ cachetools_.
123
124
 
124
125
 
125
126
  License
@@ -144,7 +145,9 @@ Licensed under the `MIT License`_.
144
145
  .. _MIT License: https://raw.github.com/tkem/cachetools/master/LICENSE
145
146
 
146
147
  .. _asyncache: https://pypi.org/project/asyncache/
147
- .. _cacheing: https://github.com/breid48/cacheing
148
+ .. _cacheing: https://pypi.org/project/cacheing/
148
149
  .. _CacheToolsUtils: https://pypi.org/project/CacheToolsUtils/
149
- .. _kids.cache: https://pypi.org/project/kids.cache/
150
150
  .. _shelved-cache: https://pypi.org/project/shelved-cache/
151
+ .. _cachetools: https://pypi.org/project/cachetools/
152
+ .. _redis: https://redis.io/
153
+ .. _memcached: https://memcached.org/
@@ -6,21 +6,35 @@ from cachetools import LRUCache, cachedmethod, keys
6
6
 
7
7
 
8
8
  class Cached:
9
+
10
+ class_cache = LRUCache(2)
11
+ class_count = 0
12
+
9
13
  def __init__(self, cache, count=0):
10
14
  self.cache = cache
11
15
  self.count = count
12
16
 
13
17
  @cachedmethod(lambda self: self.cache)
14
18
  def get(self, value):
15
- count = self.count
16
19
  self.count += 1
17
- return count
20
+ return self.count
18
21
 
19
22
  @cachedmethod(lambda self: self.cache, key=keys.typedmethodkey)
20
23
  def get_typedmethod(self, value):
21
- count = self.count
22
24
  self.count += 1
23
- return count
25
+ return self.count
26
+
27
+ @classmethod
28
+ @cachedmethod(lambda cls: cls.class_cache)
29
+ def get_classmethod(cls, value):
30
+ cls.class_count += 1
31
+ return cls.class_count
32
+
33
+ @classmethod
34
+ @cachedmethod(lambda cls: cls.class_cache, key=keys.typedmethodkey)
35
+ def get_typedclassmethod(cls, value):
36
+ cls.class_count += 1
37
+ return cls.class_count
24
38
 
25
39
 
26
40
  class Locked:
@@ -31,9 +45,8 @@ class Locked:
31
45
 
32
46
  @cachedmethod(lambda self: self.cache, lock=lambda self: self)
33
47
  def get(self, value):
34
- count = self.count
35
48
  self.count += 1
36
- return count
49
+ return self.count
37
50
 
38
51
  def __enter__(self):
39
52
  self.lock_count += 1
@@ -86,57 +99,57 @@ class CachedMethodTest(unittest.TestCase):
86
99
  def test_dict(self):
87
100
  cached = Cached({})
88
101
 
89
- self.assertEqual(cached.get(0), 0)
90
- self.assertEqual(cached.get(1), 1)
91
- self.assertEqual(cached.get(1), 1)
92
- self.assertEqual(cached.get(1.0), 1)
93
- self.assertEqual(cached.get(1.0), 1)
102
+ self.assertEqual(cached.get(0), 1)
103
+ self.assertEqual(cached.get(1), 2)
104
+ self.assertEqual(cached.get(1), 2)
105
+ self.assertEqual(cached.get(1.0), 2)
106
+ self.assertEqual(cached.get(1.0), 2)
94
107
 
95
108
  cached.cache.clear()
96
- self.assertEqual(cached.get(1), 2)
109
+ self.assertEqual(cached.get(1), 3)
97
110
 
98
111
  def test_typedmethod_dict(self):
99
112
  cached = Cached(LRUCache(maxsize=2))
100
113
 
101
- self.assertEqual(cached.get_typedmethod(0), 0)
102
- self.assertEqual(cached.get_typedmethod(1), 1)
103
- self.assertEqual(cached.get_typedmethod(1), 1)
104
- self.assertEqual(cached.get_typedmethod(1.0), 2)
105
- self.assertEqual(cached.get_typedmethod(1.0), 2)
106
- self.assertEqual(cached.get_typedmethod(0.0), 3)
107
- self.assertEqual(cached.get_typedmethod(0), 4)
114
+ self.assertEqual(cached.get_typedmethod(0), 1)
115
+ self.assertEqual(cached.get_typedmethod(1), 2)
116
+ self.assertEqual(cached.get_typedmethod(1), 2)
117
+ self.assertEqual(cached.get_typedmethod(1.0), 3)
118
+ self.assertEqual(cached.get_typedmethod(1.0), 3)
119
+ self.assertEqual(cached.get_typedmethod(0.0), 4)
120
+ self.assertEqual(cached.get_typedmethod(0), 5)
108
121
 
109
122
  def test_lru(self):
110
123
  cached = Cached(LRUCache(maxsize=2))
111
124
 
112
- self.assertEqual(cached.get(0), 0)
113
- self.assertEqual(cached.get(1), 1)
114
- self.assertEqual(cached.get(1), 1)
115
- self.assertEqual(cached.get(1.0), 1)
116
- self.assertEqual(cached.get(1.0), 1)
125
+ self.assertEqual(cached.get(0), 1)
126
+ self.assertEqual(cached.get(1), 2)
127
+ self.assertEqual(cached.get(1), 2)
128
+ self.assertEqual(cached.get(1.0), 2)
129
+ self.assertEqual(cached.get(1.0), 2)
117
130
 
118
131
  cached.cache.clear()
119
- self.assertEqual(cached.get(1), 2)
132
+ self.assertEqual(cached.get(1), 3)
120
133
 
121
134
  def test_typedmethod_lru(self):
122
135
  cached = Cached(LRUCache(maxsize=2))
123
136
 
124
- self.assertEqual(cached.get_typedmethod(0), 0)
125
- self.assertEqual(cached.get_typedmethod(1), 1)
126
- self.assertEqual(cached.get_typedmethod(1), 1)
127
- self.assertEqual(cached.get_typedmethod(1.0), 2)
128
- self.assertEqual(cached.get_typedmethod(1.0), 2)
129
- self.assertEqual(cached.get_typedmethod(0.0), 3)
130
- self.assertEqual(cached.get_typedmethod(0), 4)
137
+ self.assertEqual(cached.get_typedmethod(0), 1)
138
+ self.assertEqual(cached.get_typedmethod(1), 2)
139
+ self.assertEqual(cached.get_typedmethod(1), 2)
140
+ self.assertEqual(cached.get_typedmethod(1.0), 3)
141
+ self.assertEqual(cached.get_typedmethod(1.0), 3)
142
+ self.assertEqual(cached.get_typedmethod(0.0), 4)
143
+ self.assertEqual(cached.get_typedmethod(0), 5)
131
144
 
132
145
  def test_nospace(self):
133
146
  cached = Cached(LRUCache(maxsize=0))
134
147
 
135
- self.assertEqual(cached.get(0), 0)
136
- self.assertEqual(cached.get(1), 1)
148
+ self.assertEqual(cached.get(0), 1)
137
149
  self.assertEqual(cached.get(1), 2)
138
- self.assertEqual(cached.get(1.0), 3)
150
+ self.assertEqual(cached.get(1), 3)
139
151
  self.assertEqual(cached.get(1.0), 4)
152
+ self.assertEqual(cached.get(1.0), 5)
140
153
 
141
154
  def test_nocache(self):
142
155
  cached = Cached(None)
@@ -144,11 +157,11 @@ class CachedMethodTest(unittest.TestCase):
144
157
  with warnings.catch_warnings(record=True) as w:
145
158
  warnings.simplefilter("always")
146
159
 
147
- self.assertEqual(cached.get(0), 0)
148
- self.assertEqual(cached.get(1), 1)
160
+ self.assertEqual(cached.get(0), 1)
149
161
  self.assertEqual(cached.get(1), 2)
150
- self.assertEqual(cached.get(1.0), 3)
162
+ self.assertEqual(cached.get(1), 3)
151
163
  self.assertEqual(cached.get(1.0), 4)
164
+ self.assertEqual(cached.get(1.0), 5)
152
165
 
153
166
  self.assertEqual(len(w), 5)
154
167
  self.assertIs(w[0].category, DeprecationWarning)
@@ -166,7 +179,7 @@ class CachedMethodTest(unittest.TestCase):
166
179
 
167
180
  cached = Cached(weakref.WeakValueDictionary(), count=Int(0))
168
181
 
169
- self.assertEqual(cached.get(0), 0)
182
+ self.assertEqual(cached.get(0), 1)
170
183
  gc.collect()
171
184
  self.assertEqual(cached.get(0), 1)
172
185
 
@@ -175,44 +188,44 @@ class CachedMethodTest(unittest.TestCase):
175
188
  self.assertEqual(cached.get(1), 2)
176
189
  self.assertEqual(cached.get(1.0), 2)
177
190
 
178
- ref = cached.get_typedmethod(1)
191
+ ref = cached.get_typedmethod(2)
179
192
  self.assertEqual(ref, 3)
180
- self.assertEqual(cached.get_typedmethod(1), 3)
181
- self.assertEqual(cached.get_typedmethod(1.0), 4)
193
+ self.assertEqual(cached.get_typedmethod(1), 4)
194
+ self.assertEqual(cached.get_typedmethod(1.0), 5)
182
195
 
183
196
  cached.cache.clear()
184
- self.assertEqual(cached.get(1), 5)
197
+ self.assertEqual(cached.get(1), 6)
185
198
 
186
199
  def test_locked_dict(self):
187
200
  cached = Locked({})
188
201
 
189
- self.assertEqual(cached.get(0), 0)
202
+ self.assertEqual(cached.get(0), 1)
190
203
  self.assertEqual(cached.lock_count, 2)
191
- self.assertEqual(cached.get(1), 1)
204
+ self.assertEqual(cached.get(1), 2)
192
205
  self.assertEqual(cached.lock_count, 4)
193
- self.assertEqual(cached.get(1), 1)
206
+ self.assertEqual(cached.get(1), 2)
194
207
  self.assertEqual(cached.lock_count, 5)
195
- self.assertEqual(cached.get(1.0), 1)
208
+ self.assertEqual(cached.get(1.0), 2)
196
209
  self.assertEqual(cached.lock_count, 6)
197
- self.assertEqual(cached.get(1.0), 1)
210
+ self.assertEqual(cached.get(1.0), 2)
198
211
  self.assertEqual(cached.lock_count, 7)
199
212
 
200
213
  cached.cache.clear()
201
- self.assertEqual(cached.get(1), 2)
214
+ self.assertEqual(cached.get(1), 3)
202
215
  self.assertEqual(cached.lock_count, 9)
203
216
 
204
217
  def test_locked_nospace(self):
205
218
  cached = Locked(LRUCache(maxsize=0))
206
219
 
207
- self.assertEqual(cached.get(0), 0)
220
+ self.assertEqual(cached.get(0), 1)
208
221
  self.assertEqual(cached.lock_count, 2)
209
- self.assertEqual(cached.get(1), 1)
210
- self.assertEqual(cached.lock_count, 4)
211
222
  self.assertEqual(cached.get(1), 2)
223
+ self.assertEqual(cached.lock_count, 4)
224
+ self.assertEqual(cached.get(1), 3)
212
225
  self.assertEqual(cached.lock_count, 6)
213
- self.assertEqual(cached.get(1.0), 3)
214
- self.assertEqual(cached.lock_count, 8)
215
226
  self.assertEqual(cached.get(1.0), 4)
227
+ self.assertEqual(cached.lock_count, 8)
228
+ self.assertEqual(cached.get(1.0), 5)
216
229
  self.assertEqual(cached.lock_count, 10)
217
230
 
218
231
  def test_locked_nocache(self):
@@ -221,11 +234,11 @@ class CachedMethodTest(unittest.TestCase):
221
234
  with warnings.catch_warnings(record=True) as w:
222
235
  warnings.simplefilter("always")
223
236
 
224
- self.assertEqual(cached.get(0), 0)
225
- self.assertEqual(cached.get(1), 1)
237
+ self.assertEqual(cached.get(0), 1)
226
238
  self.assertEqual(cached.get(1), 2)
227
- self.assertEqual(cached.get(1.0), 3)
239
+ self.assertEqual(cached.get(1), 3)
228
240
  self.assertEqual(cached.get(1.0), 4)
241
+ self.assertEqual(cached.get(1.0), 5)
229
242
  self.assertEqual(cached.lock_count, 0)
230
243
 
231
244
  self.assertEqual(len(w), 5)
@@ -234,29 +247,29 @@ class CachedMethodTest(unittest.TestCase):
234
247
  def test_condition_dict(self):
235
248
  cached = Conditioned({})
236
249
 
237
- self.assertEqual(cached.get(0), 0)
250
+ self.assertEqual(cached.get(0), 1)
238
251
  self.assertEqual(cached.lock_count, 3)
239
252
  self.assertEqual(cached.wait_count, 1)
240
253
  self.assertEqual(cached.notify_count, 1)
241
- self.assertEqual(cached.get(1), 1)
254
+ self.assertEqual(cached.get(1), 2)
242
255
  self.assertEqual(cached.lock_count, 6)
243
256
  self.assertEqual(cached.wait_count, 2)
244
257
  self.assertEqual(cached.notify_count, 2)
245
- self.assertEqual(cached.get(1), 1)
258
+ self.assertEqual(cached.get(1), 2)
246
259
  self.assertEqual(cached.lock_count, 7)
247
260
  self.assertEqual(cached.wait_count, 3)
248
261
  self.assertEqual(cached.notify_count, 2)
249
- self.assertEqual(cached.get(1.0), 1)
262
+ self.assertEqual(cached.get(1.0), 2)
250
263
  self.assertEqual(cached.lock_count, 8)
251
264
  self.assertEqual(cached.wait_count, 4)
252
265
  self.assertEqual(cached.notify_count, 2)
253
- self.assertEqual(cached.get(1.0), 1)
266
+ self.assertEqual(cached.get(1.0), 2)
254
267
  self.assertEqual(cached.lock_count, 9)
255
268
  self.assertEqual(cached.wait_count, 5)
256
269
  self.assertEqual(cached.notify_count, 2)
257
270
 
258
271
  cached.cache.clear()
259
- self.assertEqual(cached.get(1), 2)
272
+ self.assertEqual(cached.get(1), 3)
260
273
  self.assertEqual(cached.lock_count, 12)
261
274
  self.assertEqual(cached.wait_count, 6)
262
275
  self.assertEqual(cached.notify_count, 3)
@@ -264,23 +277,23 @@ class CachedMethodTest(unittest.TestCase):
264
277
  def test_condition_nospace(self):
265
278
  cached = Conditioned(LRUCache(maxsize=0))
266
279
 
267
- self.assertEqual(cached.get(0), 0)
280
+ self.assertEqual(cached.get(0), 1)
268
281
  self.assertEqual(cached.lock_count, 3)
269
282
  self.assertEqual(cached.wait_count, 1)
270
283
  self.assertEqual(cached.notify_count, 1)
271
- self.assertEqual(cached.get(1), 1)
284
+ self.assertEqual(cached.get(1), 2)
272
285
  self.assertEqual(cached.lock_count, 6)
273
286
  self.assertEqual(cached.wait_count, 2)
274
287
  self.assertEqual(cached.notify_count, 2)
275
- self.assertEqual(cached.get(1), 2)
288
+ self.assertEqual(cached.get(1), 3)
276
289
  self.assertEqual(cached.lock_count, 9)
277
290
  self.assertEqual(cached.wait_count, 3)
278
291
  self.assertEqual(cached.notify_count, 3)
279
- self.assertEqual(cached.get(1.0), 3)
292
+ self.assertEqual(cached.get(1.0), 4)
280
293
  self.assertEqual(cached.lock_count, 12)
281
294
  self.assertEqual(cached.wait_count, 4)
282
295
  self.assertEqual(cached.notify_count, 4)
283
- self.assertEqual(cached.get(1.0), 4)
296
+ self.assertEqual(cached.get(1.0), 5)
284
297
  self.assertEqual(cached.lock_count, 15)
285
298
  self.assertEqual(cached.wait_count, 5)
286
299
  self.assertEqual(cached.notify_count, 5)
@@ -291,11 +304,11 @@ class CachedMethodTest(unittest.TestCase):
291
304
  with warnings.catch_warnings(record=True) as w:
292
305
  warnings.simplefilter("always")
293
306
 
294
- self.assertEqual(cached.get(0), 0)
295
- self.assertEqual(cached.get(1), 1)
307
+ self.assertEqual(cached.get(0), 1)
296
308
  self.assertEqual(cached.get(1), 2)
297
- self.assertEqual(cached.get(1.0), 3)
309
+ self.assertEqual(cached.get(1), 3)
298
310
  self.assertEqual(cached.get(1.0), 4)
311
+ self.assertEqual(cached.get(1.0), 5)
299
312
  self.assertEqual(cached.lock_count, 0)
300
313
  self.assertEqual(cached.wait_count, 0)
301
314
  self.assertEqual(cached.notify_count, 0)
@@ -317,11 +330,11 @@ class CachedMethodTest(unittest.TestCase):
317
330
  cached = Cached(cache)
318
331
 
319
332
  self.assertEqual(len(cache), 0)
320
- self.assertEqual(Cached.get.__wrapped__(cached, 0), 0)
333
+ self.assertEqual(Cached.get.__wrapped__(cached, 0), 1)
321
334
  self.assertEqual(len(cache), 0)
322
- self.assertEqual(cached.get(0), 1)
335
+ self.assertEqual(cached.get(0), 2)
323
336
  self.assertEqual(len(cache), 1)
324
- self.assertEqual(cached.get(0), 1)
337
+ self.assertEqual(cached.get(0), 2)
325
338
  self.assertEqual(len(cache), 1)
326
339
 
327
340
  def test_attributes(self):
@@ -355,7 +368,7 @@ class CachedMethodTest(unittest.TestCase):
355
368
  cache = {}
356
369
  cached = Cached(cache)
357
370
 
358
- self.assertEqual(cached.get(0), 0)
371
+ self.assertEqual(cached.get(0), 1)
359
372
  self.assertEqual(len(cache), 1)
360
373
  Cached.get.cache_clear(cached)
361
374
  self.assertEqual(len(cache), 0)
@@ -364,7 +377,7 @@ class CachedMethodTest(unittest.TestCase):
364
377
  cache = {}
365
378
  cached = Locked(cache)
366
379
 
367
- self.assertEqual(cached.get(0), 0)
380
+ self.assertEqual(cached.get(0), 1)
368
381
  self.assertEqual(len(cache), 1)
369
382
  self.assertEqual(cached.lock_count, 2)
370
383
  Locked.get.cache_clear(cached)
@@ -375,9 +388,47 @@ class CachedMethodTest(unittest.TestCase):
375
388
  cache = {}
376
389
  cached = Conditioned(cache)
377
390
 
378
- self.assertEqual(cached.get(0), 0)
391
+ self.assertEqual(cached.get(0), 1)
379
392
  self.assertEqual(len(cache), 1)
380
393
  self.assertEqual(cached.lock_count, 3)
381
394
  Conditioned.get.cache_clear(cached)
382
395
  self.assertEqual(len(cache), 0)
383
396
  self.assertEqual(cached.lock_count, 4)
397
+
398
+
399
+ class CachedClassMethodTest(unittest.TestCase):
400
+
401
+ def test(self):
402
+ Cached.class_cache = LRUCache(2)
403
+ Cached.class_count = 0
404
+ cached = Cached(None)
405
+
406
+ self.assertEqual(cached.get_classmethod(0), 1)
407
+ self.assertEqual(Cached.get_classmethod(0), 1)
408
+ self.assertEqual(cached.get_classmethod(1), 2)
409
+ self.assertEqual(Cached.get_classmethod(1), 2)
410
+ self.assertEqual(cached.get_classmethod(1), 2)
411
+ self.assertEqual(Cached.get_classmethod(1), 2)
412
+ self.assertEqual(cached.get_classmethod(1.0), 2)
413
+ self.assertEqual(Cached.get_classmethod(1.0), 2)
414
+ self.assertEqual(Cached.get_classmethod(1.1), 3)
415
+ self.assertEqual(cached.get_classmethod(1.1), 3)
416
+
417
+ cached.class_cache.clear()
418
+ self.assertEqual(cached.get_classmethod(1), 4)
419
+
420
+ def test_typedmethod(self):
421
+ Cached.class_cache = LRUCache(2)
422
+ Cached.class_count = 0
423
+ cached = Cached(None)
424
+
425
+ self.assertEqual(cached.get_typedclassmethod(0), 1)
426
+ self.assertEqual(Cached.get_typedclassmethod(0), 1)
427
+ self.assertEqual(cached.get_typedclassmethod(1), 2)
428
+ self.assertEqual(Cached.get_typedclassmethod(1), 2)
429
+ self.assertEqual(cached.get_typedclassmethod(1.0), 3)
430
+ self.assertEqual(Cached.get_typedclassmethod(1.0), 3)
431
+ self.assertEqual(cached.get_typedclassmethod(0.0), 4)
432
+ self.assertEqual(Cached.get_typedclassmethod(0.0), 4)
433
+ self.assertEqual(Cached.get_typedclassmethod(0), 5)
434
+ self.assertEqual(cached.get_typedclassmethod(0), 5)
@@ -5,7 +5,7 @@ from cachetools import FIFOCache
5
5
  from . import CacheTestMixin
6
6
 
7
7
 
8
- class LRUCacheTest(unittest.TestCase, CacheTestMixin):
8
+ class FIFOCacheTest(unittest.TestCase, CacheTestMixin):
9
9
  Cache = FIFOCache
10
10
 
11
11
  def test_fifo(self):
@@ -54,3 +54,15 @@ class LRUCacheTest(unittest.TestCase, CacheTestMixin):
54
54
  cache[4] = 4
55
55
  self.assertEqual(len(cache), 1)
56
56
  self.assertEqual(cache[3], 3)
57
+
58
+ def test_fifo_update_existing(self):
59
+ cache = FIFOCache(maxsize=2)
60
+
61
+ cache[1] = 1
62
+ cache[2] = 2
63
+ cache[1] = "updated"
64
+ cache[3] = 3
65
+
66
+ self.assertEqual(cache[1], "updated")
67
+ self.assertIn(3, cache)
68
+ self.assertNotIn(2, cache)
@@ -76,6 +76,11 @@ class DecoratorTestMixin:
76
76
  self.assertEqual(cached.cache_info(), (2, 1, 128, 1))
77
77
 
78
78
  def test_decorator_needs_rlock(self):
79
+ """This will deadlock on a cache that uses a regular lock.
80
+ https://github.com/python/cpython/blob/3.13/Lib/test/test_functools.py#L1791
81
+
82
+ """
83
+
79
84
  cached = self.decorator(lambda n: n)
80
85
 
81
86
  class RecursiveEquals:
@@ -26,6 +26,11 @@ class LFUCacheTest(unittest.TestCase, CacheTestMixin):
26
26
  self.assertEqual(cache[4], 4)
27
27
  self.assertEqual(cache[1], 1)
28
28
 
29
+ cache[1]
30
+ self.assertEqual(len(cache), 2)
31
+ self.assertEqual(cache[1], 1)
32
+ self.assertEqual(cache[4], 4)
33
+
29
34
  def test_lfu_getsizeof(self):
30
35
  cache = LFUCache(maxsize=3, getsizeof=lambda x: x)
31
36
 
@@ -47,3 +52,15 @@ class LFUCacheTest(unittest.TestCase, CacheTestMixin):
47
52
  cache[4] = 4
48
53
  self.assertEqual(len(cache), 1)
49
54
  self.assertEqual(cache[3], 3)
55
+
56
+ def test_lfu_update_existing(self):
57
+ cache = LFUCache(maxsize=2)
58
+
59
+ cache[1] = 1
60
+ cache[2] = 2
61
+ cache[1] = "updated"
62
+ cache[3] = 3
63
+
64
+ self.assertEqual(cache[1], "updated")
65
+ self.assertIn(3, cache)
66
+ self.assertNotIn(2, cache)
@@ -54,3 +54,15 @@ class LRUCacheTest(unittest.TestCase, CacheTestMixin):
54
54
  cache[4] = 4
55
55
  self.assertEqual(len(cache), 1)
56
56
  self.assertEqual(cache[3], 3)
57
+
58
+ def test_lru_update_existing(self):
59
+ cache = LRUCache(maxsize=2)
60
+
61
+ cache[1] = 1
62
+ cache[2] = 2
63
+ cache[1] = "updated"
64
+ cache[3] = 3
65
+
66
+ self.assertEqual(cache[1], "updated")
67
+ self.assertIn(3, cache)
68
+ self.assertNotIn(2, cache)
@@ -0,0 +1,83 @@
1
+ import random
2
+ import unittest
3
+
4
+ from cachetools import RRCache
5
+
6
+ from . import CacheTestMixin
7
+
8
+
9
+ class RRCacheTest(unittest.TestCase, CacheTestMixin):
10
+ Cache = RRCache
11
+
12
+ def test_rr(self):
13
+ cache = RRCache(maxsize=2, choice=min)
14
+ self.assertEqual(min, cache.choice)
15
+
16
+ cache[1] = 1
17
+ cache[2] = 2
18
+ cache[3] = 3
19
+
20
+ self.assertEqual(2, len(cache))
21
+ self.assertEqual(2, cache[2])
22
+ self.assertEqual(3, cache[3])
23
+ self.assertNotIn(1, cache)
24
+
25
+ cache[0] = 0
26
+ self.assertEqual(2, len(cache))
27
+ self.assertEqual(0, cache[0])
28
+ self.assertEqual(3, cache[3])
29
+ self.assertNotIn(2, cache)
30
+
31
+ cache[4] = 4
32
+ self.assertEqual(2, len(cache))
33
+ self.assertEqual(3, cache[3])
34
+ self.assertEqual(4, cache[4])
35
+ self.assertNotIn(0, cache)
36
+
37
+ def test_rr_getsizeof(self):
38
+ cache = RRCache(maxsize=3, choice=min, getsizeof=lambda x: x)
39
+
40
+ cache[1] = 1
41
+ cache[2] = 2
42
+
43
+ self.assertEqual(len(cache), 2)
44
+ self.assertEqual(cache[1], 1)
45
+ self.assertEqual(cache[2], 2)
46
+
47
+ cache[3] = 3
48
+
49
+ self.assertEqual(len(cache), 1)
50
+ self.assertEqual(cache[3], 3)
51
+ self.assertNotIn(1, cache)
52
+ self.assertNotIn(2, cache)
53
+
54
+ with self.assertRaises(ValueError):
55
+ cache[4] = 4
56
+ self.assertEqual(len(cache), 1)
57
+ self.assertEqual(cache[3], 3)
58
+
59
+ def test_rr_update_existing(self):
60
+ cache = RRCache(maxsize=2, choice=min)
61
+
62
+ cache[1] = 1
63
+ cache[2] = 2
64
+ cache[1] = "updated"
65
+ cache[3] = 3
66
+
67
+ self.assertIn(2, cache)
68
+ self.assertIn(3, cache)
69
+ self.assertNotIn(1, cache)
70
+
71
+ def test_rr_bad_choice(self):
72
+ def bad_choice(seq):
73
+ raise ValueError("test error")
74
+
75
+ cache = RRCache(maxsize=2, choice=bad_choice)
76
+ cache[1] = 1
77
+ cache[2] = 2
78
+ with self.assertRaises(ValueError):
79
+ cache[3] = 3
80
+
81
+ def test_rr_default_choice(self):
82
+ cache = RRCache(maxsize=2)
83
+ self.assertIs(cache.choice, random.choice)
@@ -1,34 +0,0 @@
1
- import unittest
2
-
3
- from cachetools import RRCache
4
-
5
- from . import CacheTestMixin
6
-
7
-
8
- class RRCacheTest(unittest.TestCase, CacheTestMixin):
9
- Cache = RRCache
10
-
11
- def test_rr(self):
12
- cache = RRCache(maxsize=2, choice=min)
13
- self.assertEqual(min, cache.choice)
14
-
15
- cache[1] = 1
16
- cache[2] = 2
17
- cache[3] = 3
18
-
19
- self.assertEqual(2, len(cache))
20
- self.assertEqual(2, cache[2])
21
- self.assertEqual(3, cache[3])
22
- self.assertNotIn(1, cache)
23
-
24
- cache[0] = 0
25
- self.assertEqual(2, len(cache))
26
- self.assertEqual(0, cache[0])
27
- self.assertEqual(3, cache[3])
28
- self.assertNotIn(2, cache)
29
-
30
- cache[4] = 4
31
- self.assertEqual(2, len(cache))
32
- self.assertEqual(3, cache[3])
33
- self.assertEqual(4, cache[4])
34
- self.assertNotIn(0, 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