cachebox 5.2.1__cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl
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.
- cachebox/__init__.py +26 -0
- cachebox/_cachebox.py +2198 -0
- cachebox/_core.cpython-314t-i386-linux-gnu.so +0 -0
- cachebox/_core.pyi +83 -0
- cachebox/py.typed +0 -0
- cachebox/utils.py +599 -0
- cachebox-5.2.1.dist-info/METADATA +880 -0
- cachebox-5.2.1.dist-info/RECORD +10 -0
- cachebox-5.2.1.dist-info/WHEEL +5 -0
- cachebox-5.2.1.dist-info/licenses/LICENSE +21 -0
cachebox/_cachebox.py
ADDED
|
@@ -0,0 +1,2198 @@
|
|
|
1
|
+
import copy as _std_copy
|
|
2
|
+
import typing
|
|
3
|
+
from datetime import datetime, timedelta
|
|
4
|
+
|
|
5
|
+
from . import _core
|
|
6
|
+
from ._core import BaseCacheImpl
|
|
7
|
+
|
|
8
|
+
KT = typing.TypeVar("KT")
|
|
9
|
+
VT = typing.TypeVar("VT")
|
|
10
|
+
DT = typing.TypeVar("DT")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _items_to_str(items: typing.Iterable[typing.Any], length) -> str:
|
|
14
|
+
if length <= 50:
|
|
15
|
+
return "{" + ", ".join(f"{k!r}: {v!r}" for k, v in items) + "}"
|
|
16
|
+
|
|
17
|
+
c = 0
|
|
18
|
+
left = []
|
|
19
|
+
|
|
20
|
+
while c < length:
|
|
21
|
+
k, v = next(items) # type: ignore[call-overload]
|
|
22
|
+
|
|
23
|
+
if c <= 50:
|
|
24
|
+
left.append(f"{k!r}: {v!r}")
|
|
25
|
+
|
|
26
|
+
else:
|
|
27
|
+
break
|
|
28
|
+
|
|
29
|
+
c += 1
|
|
30
|
+
|
|
31
|
+
return "{%s, ... %d more ...}" % (", ".join(left), length - c)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class IteratorView(typing.Generic[VT]):
|
|
35
|
+
__slots__ = ("iterator", "func")
|
|
36
|
+
|
|
37
|
+
def __init__(self, iterator, func: typing.Callable[[tuple], typing.Any]):
|
|
38
|
+
self.iterator = iterator
|
|
39
|
+
self.func = func
|
|
40
|
+
|
|
41
|
+
def __iter__(self):
|
|
42
|
+
self.iterator = self.iterator.__iter__()
|
|
43
|
+
return self
|
|
44
|
+
|
|
45
|
+
def __next__(self) -> VT:
|
|
46
|
+
return self.func(self.iterator.__next__())
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class Cache(BaseCacheImpl[KT, VT]):
|
|
50
|
+
"""
|
|
51
|
+
A thread-safe, memory-efficient hashmap-like cache with configurable maximum size.
|
|
52
|
+
|
|
53
|
+
Provides a flexible key-value storage mechanism with:
|
|
54
|
+
- Configurable maximum size (zero means unlimited)
|
|
55
|
+
- Lower memory usage compared to standard dict
|
|
56
|
+
- Thread-safe operations
|
|
57
|
+
- Useful memory management methods
|
|
58
|
+
|
|
59
|
+
Differs from standard dict by:
|
|
60
|
+
- Being thread-safe
|
|
61
|
+
- Unordered storage
|
|
62
|
+
- Size limitation
|
|
63
|
+
- Memory efficiency
|
|
64
|
+
- Additional cache management methods
|
|
65
|
+
|
|
66
|
+
Supports initialization with optional initial data and capacity,
|
|
67
|
+
and provides dictionary-like access with additional cache-specific operations.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
__slots__ = ("_raw",)
|
|
71
|
+
|
|
72
|
+
def __init__(
|
|
73
|
+
self,
|
|
74
|
+
maxsize: int,
|
|
75
|
+
iterable: typing.Union[dict, typing.Iterable[tuple], None] = None,
|
|
76
|
+
*,
|
|
77
|
+
capacity: int = 0,
|
|
78
|
+
maxmemory: int = 0,
|
|
79
|
+
) -> None:
|
|
80
|
+
"""
|
|
81
|
+
Initialize a new Cache instance.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
maxsize (int): Maximum number of elements the cache can hold. Zero means unlimited.
|
|
85
|
+
iterable (Union[Cache, dict, tuple, Generator, None], optional): Initial data to populate the cache. Defaults to None.
|
|
86
|
+
capacity (int, optional): Pre-allocate hash table capacity to minimize reallocations. Defaults to 0.
|
|
87
|
+
maxmemory (int, optional): Maximum memory (bytes) allowed for cached entries. Zero means unlimited.
|
|
88
|
+
On PyPy, it works same as `maxsize` if objects do not support `__sizeof__`
|
|
89
|
+
method.
|
|
90
|
+
|
|
91
|
+
Creates a new cache with specified size constraints and optional initial data. The cache can be pre-sized
|
|
92
|
+
to improve performance when the number of expected elements is known in advance.
|
|
93
|
+
"""
|
|
94
|
+
self._raw = _core.Cache(maxsize, capacity=capacity, maxmemory=maxmemory)
|
|
95
|
+
|
|
96
|
+
if iterable is not None:
|
|
97
|
+
self.update(iterable)
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def maxsize(self) -> int:
|
|
101
|
+
return self._raw.maxsize()
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def maxmemory(self) -> int:
|
|
105
|
+
return self._raw.maxmemory()
|
|
106
|
+
|
|
107
|
+
def capacity(self) -> int:
|
|
108
|
+
"""Returns the number of elements the map can hold without reallocating."""
|
|
109
|
+
return self._raw.capacity()
|
|
110
|
+
|
|
111
|
+
def memory(self) -> int:
|
|
112
|
+
"""Returns the total estimated memory usage of cached entries in bytes."""
|
|
113
|
+
return self._raw.memory()
|
|
114
|
+
|
|
115
|
+
def __len__(self) -> int:
|
|
116
|
+
return len(self._raw)
|
|
117
|
+
|
|
118
|
+
def __sizeof__(self): # pragma: no cover
|
|
119
|
+
return self._raw.__sizeof__()
|
|
120
|
+
|
|
121
|
+
def __contains__(self, key: KT) -> bool:
|
|
122
|
+
return key in self._raw
|
|
123
|
+
|
|
124
|
+
def __bool__(self) -> bool:
|
|
125
|
+
return not self.is_empty()
|
|
126
|
+
|
|
127
|
+
def is_empty(self) -> bool:
|
|
128
|
+
return self._raw.is_empty()
|
|
129
|
+
|
|
130
|
+
def is_full(self) -> bool:
|
|
131
|
+
return self._raw.is_full()
|
|
132
|
+
|
|
133
|
+
def insert(self, key: KT, value: VT) -> typing.Optional[VT]:
|
|
134
|
+
"""
|
|
135
|
+
Equals to `self[key] = value`, but returns a value:
|
|
136
|
+
|
|
137
|
+
- If the cache did not have this key present, None is returned.
|
|
138
|
+
- If the cache did have this key present, the value is updated,
|
|
139
|
+
and the old value is returned. The key is not updated, though;
|
|
140
|
+
|
|
141
|
+
Note: raises `OverflowError` if the cache reached the maxsize limit,
|
|
142
|
+
because this class does not have any algorithm.
|
|
143
|
+
"""
|
|
144
|
+
return self._raw.insert(key, value)
|
|
145
|
+
|
|
146
|
+
def get(self, key: KT, default: typing.Optional[DT] = None) -> typing.Union[VT, DT]:
|
|
147
|
+
"""
|
|
148
|
+
Retrieves the value for a given key from the cache.
|
|
149
|
+
|
|
150
|
+
Returns the value associated with the key if present, otherwise returns the specified default value.
|
|
151
|
+
Equivalent to `self[key]`, but provides a fallback default if the key is not found.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
key: The key to look up in the cache.
|
|
155
|
+
default: The value to return if the key is not present in the cache. Defaults to None.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
The value associated with the key, or the default value if the key is not found.
|
|
159
|
+
"""
|
|
160
|
+
try:
|
|
161
|
+
return self._raw.get(key)
|
|
162
|
+
except _core.CoreKeyError:
|
|
163
|
+
return default # type: ignore[return-value]
|
|
164
|
+
|
|
165
|
+
def pop(self, key: KT, default: typing.Optional[DT] = None) -> typing.Union[VT, DT]:
|
|
166
|
+
"""
|
|
167
|
+
Removes specified key and return the corresponding value. If the key is not found, returns the `default`.
|
|
168
|
+
"""
|
|
169
|
+
try:
|
|
170
|
+
return self._raw.remove(key)
|
|
171
|
+
except _core.CoreKeyError:
|
|
172
|
+
return default # type: ignore[return-value]
|
|
173
|
+
|
|
174
|
+
def setdefault(self, key: KT, default: typing.Optional[DT] = None) -> typing.Union[VT, DT]:
|
|
175
|
+
"""
|
|
176
|
+
Inserts key with a value of default if key is not in the cache. Return the value for key if key is
|
|
177
|
+
in the cache, else `default`.
|
|
178
|
+
"""
|
|
179
|
+
return self._raw.setdefault(key, default)
|
|
180
|
+
|
|
181
|
+
def popitem(self) -> typing.NoReturn: # pragma: no cover
|
|
182
|
+
raise NotImplementedError()
|
|
183
|
+
|
|
184
|
+
def drain(self, n: int) -> typing.NoReturn: # pragma: no cover
|
|
185
|
+
raise NotImplementedError()
|
|
186
|
+
|
|
187
|
+
def update(self, iterable: typing.Union[dict, typing.Iterable[tuple]]) -> None:
|
|
188
|
+
"""
|
|
189
|
+
Updates the cache with elements from a dictionary or an iterable object of key/value pairs.
|
|
190
|
+
|
|
191
|
+
Note: raises `OverflowError` if the cache reached the maxsize limit.
|
|
192
|
+
"""
|
|
193
|
+
if hasattr(iterable, "items"):
|
|
194
|
+
iterable = iterable.items()
|
|
195
|
+
|
|
196
|
+
self._raw.update(iterable)
|
|
197
|
+
|
|
198
|
+
def __setitem__(self, key: KT, value: VT) -> None:
|
|
199
|
+
self.insert(key, value)
|
|
200
|
+
|
|
201
|
+
def __getitem__(self, key: KT) -> VT:
|
|
202
|
+
try:
|
|
203
|
+
return self._raw.get(key)
|
|
204
|
+
except _core.CoreKeyError:
|
|
205
|
+
raise KeyError(key) from None
|
|
206
|
+
|
|
207
|
+
def __delitem__(self, key: KT) -> None:
|
|
208
|
+
try:
|
|
209
|
+
self._raw.remove(key)
|
|
210
|
+
except _core.CoreKeyError:
|
|
211
|
+
raise KeyError(key) from None
|
|
212
|
+
|
|
213
|
+
def __eq__(self, other) -> bool:
|
|
214
|
+
if not isinstance(other, Cache):
|
|
215
|
+
return False # pragma: no cover
|
|
216
|
+
|
|
217
|
+
return self._raw == other._raw
|
|
218
|
+
|
|
219
|
+
def __ne__(self, other) -> bool:
|
|
220
|
+
if not isinstance(other, Cache):
|
|
221
|
+
return False # pragma: no cover
|
|
222
|
+
|
|
223
|
+
return self._raw != other._raw
|
|
224
|
+
|
|
225
|
+
def shrink_to_fit(self) -> None:
|
|
226
|
+
"""Shrinks the cache to fit len(self) elements."""
|
|
227
|
+
self._raw.shrink_to_fit()
|
|
228
|
+
|
|
229
|
+
def clear(self, *, reuse: bool = False) -> None:
|
|
230
|
+
"""
|
|
231
|
+
Removes all items from cache.
|
|
232
|
+
|
|
233
|
+
If reuse is True, will not free the memory for reusing in the future.
|
|
234
|
+
"""
|
|
235
|
+
self._raw.clear(reuse)
|
|
236
|
+
|
|
237
|
+
def items(self) -> IteratorView[typing.Tuple[KT, VT]]:
|
|
238
|
+
"""
|
|
239
|
+
Returns an iterable object of the cache's items (key-value pairs).
|
|
240
|
+
|
|
241
|
+
Notes:
|
|
242
|
+
- You should not make any changes in cache while using this iterable object.
|
|
243
|
+
- Items are not ordered.
|
|
244
|
+
"""
|
|
245
|
+
return IteratorView(self._raw.items(), lambda x: x)
|
|
246
|
+
|
|
247
|
+
def keys(self) -> IteratorView[KT]:
|
|
248
|
+
"""
|
|
249
|
+
Returns an iterable object of the cache's keys.
|
|
250
|
+
|
|
251
|
+
Notes:
|
|
252
|
+
- You should not make any changes in cache while using this iterable object.
|
|
253
|
+
- Keys are not ordered.
|
|
254
|
+
"""
|
|
255
|
+
return IteratorView(self._raw.items(), lambda x: x[0])
|
|
256
|
+
|
|
257
|
+
def values(self) -> IteratorView[VT]:
|
|
258
|
+
"""
|
|
259
|
+
Returns an iterable object of the cache's values.
|
|
260
|
+
|
|
261
|
+
Notes:
|
|
262
|
+
- You should not make any changes in cache while using this iterable object.
|
|
263
|
+
- Values are not ordered.
|
|
264
|
+
"""
|
|
265
|
+
return IteratorView(self._raw.items(), lambda x: x[1])
|
|
266
|
+
|
|
267
|
+
def copy(self) -> "Cache[KT, VT]":
|
|
268
|
+
"""Returns a shallow copy of the cache"""
|
|
269
|
+
return self.__copy__()
|
|
270
|
+
|
|
271
|
+
def __copy__(self) -> "Cache[KT, VT]":
|
|
272
|
+
cls = type(self)
|
|
273
|
+
copied = cls.__new__(cls)
|
|
274
|
+
copied._raw = _std_copy.copy(self._raw)
|
|
275
|
+
return copied
|
|
276
|
+
|
|
277
|
+
def __deepcopy__(self, memo) -> "Cache[KT, VT]":
|
|
278
|
+
cls = type(self)
|
|
279
|
+
copied = cls.__new__(cls)
|
|
280
|
+
copied._raw = _std_copy.deepcopy(self._raw, memo)
|
|
281
|
+
return copied
|
|
282
|
+
|
|
283
|
+
def __iter__(self) -> IteratorView[KT]:
|
|
284
|
+
return self.keys()
|
|
285
|
+
|
|
286
|
+
def __repr__(self) -> str:
|
|
287
|
+
cls = type(self)
|
|
288
|
+
|
|
289
|
+
return "%s.%s[%d/%d](%s)" % (
|
|
290
|
+
cls.__module__,
|
|
291
|
+
cls.__name__,
|
|
292
|
+
len(self._raw),
|
|
293
|
+
self._raw.maxsize(),
|
|
294
|
+
_items_to_str(self._raw.items(), len(self._raw)),
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
class FIFOCache(BaseCacheImpl[KT, VT]):
|
|
299
|
+
"""
|
|
300
|
+
A First-In-First-Out (FIFO) cache implementation with configurable maximum size and optional initial capacity.
|
|
301
|
+
|
|
302
|
+
This cache provides a fixed-size container that automatically removes the oldest items when the maximum size is reached.
|
|
303
|
+
Supports various operations like insertion, retrieval, deletion, and iteration.
|
|
304
|
+
|
|
305
|
+
Attributes:
|
|
306
|
+
maxsize: The maximum number of items the cache can hold.
|
|
307
|
+
capacity: The initial capacity of the cache before resizing.
|
|
308
|
+
|
|
309
|
+
Key features:
|
|
310
|
+
- Deterministic item eviction order (oldest items removed first)
|
|
311
|
+
- Efficient key-value storage and retrieval
|
|
312
|
+
- Supports dictionary-like operations
|
|
313
|
+
- Allows optional initial data population
|
|
314
|
+
"""
|
|
315
|
+
|
|
316
|
+
__slots__ = ("_raw",)
|
|
317
|
+
|
|
318
|
+
def __init__(
|
|
319
|
+
self,
|
|
320
|
+
maxsize: int,
|
|
321
|
+
iterable: typing.Union[typing.Union[dict, typing.Iterable[tuple]], None] = None,
|
|
322
|
+
*,
|
|
323
|
+
capacity: int = 0,
|
|
324
|
+
maxmemory: int = 0,
|
|
325
|
+
) -> None:
|
|
326
|
+
"""
|
|
327
|
+
Initialize a new FIFOCache instance.
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
maxsize: The maximum number of items the cache can hold.
|
|
331
|
+
iterable: Optional initial data to populate the cache. Can be another FIFOCache,
|
|
332
|
+
a dictionary, tuple, generator, or None.
|
|
333
|
+
capacity: Optional initial capacity of the cache before resizing. Defaults to 0.
|
|
334
|
+
maxmemory: Maximum memory (bytes) allowed for cached entries. Zero means unlimited.
|
|
335
|
+
When maxmemory is set, updating an existing key can evict the updated key
|
|
336
|
+
if it is the oldest entry.
|
|
337
|
+
"""
|
|
338
|
+
self._raw = _core.FIFOCache(maxsize, capacity=capacity, maxmemory=maxmemory)
|
|
339
|
+
|
|
340
|
+
if iterable is not None:
|
|
341
|
+
self.update(iterable)
|
|
342
|
+
|
|
343
|
+
@property
|
|
344
|
+
def maxsize(self) -> int:
|
|
345
|
+
return self._raw.maxsize()
|
|
346
|
+
|
|
347
|
+
@property
|
|
348
|
+
def maxmemory(self) -> int:
|
|
349
|
+
return self._raw.maxmemory()
|
|
350
|
+
|
|
351
|
+
def capacity(self) -> int:
|
|
352
|
+
"""Returns the number of elements the map can hold without reallocating."""
|
|
353
|
+
return self._raw.capacity()
|
|
354
|
+
|
|
355
|
+
def memory(self) -> int:
|
|
356
|
+
"""Returns the total estimated memory usage of cached entries in bytes."""
|
|
357
|
+
return self._raw.memory()
|
|
358
|
+
|
|
359
|
+
def __len__(self) -> int:
|
|
360
|
+
return len(self._raw)
|
|
361
|
+
|
|
362
|
+
def __sizeof__(self): # pragma: no cover
|
|
363
|
+
return self._raw.__sizeof__()
|
|
364
|
+
|
|
365
|
+
def __contains__(self, key: KT) -> bool:
|
|
366
|
+
return key in self._raw
|
|
367
|
+
|
|
368
|
+
def __bool__(self) -> bool:
|
|
369
|
+
return not self.is_empty()
|
|
370
|
+
|
|
371
|
+
def is_empty(self) -> bool:
|
|
372
|
+
return self._raw.is_empty()
|
|
373
|
+
|
|
374
|
+
def is_full(self) -> bool:
|
|
375
|
+
return self._raw.is_full()
|
|
376
|
+
|
|
377
|
+
def insert(self, key: KT, value: VT) -> typing.Optional[VT]:
|
|
378
|
+
"""
|
|
379
|
+
Inserts a key-value pair into the cache, returning the previous value if the key existed.
|
|
380
|
+
|
|
381
|
+
Equivalent to `self[key] = value`, but with additional return value semantics:
|
|
382
|
+
|
|
383
|
+
- If the key was not previously in the cache, returns None.
|
|
384
|
+
- If the key was already present, updates the value and returns the old value.
|
|
385
|
+
The key itself is not modified.
|
|
386
|
+
|
|
387
|
+
Args:
|
|
388
|
+
key: The key to insert.
|
|
389
|
+
value: The value to associate with the key.
|
|
390
|
+
|
|
391
|
+
Returns:
|
|
392
|
+
The previous value associated with the key, or None if the key was not present.
|
|
393
|
+
"""
|
|
394
|
+
return self._raw.insert(key, value)
|
|
395
|
+
|
|
396
|
+
def get(self, key: KT, default: typing.Optional[DT] = None) -> typing.Union[VT, DT]:
|
|
397
|
+
"""
|
|
398
|
+
Retrieves the value for a given key from the cache.
|
|
399
|
+
|
|
400
|
+
Returns the value associated with the key if present, otherwise returns the specified default value.
|
|
401
|
+
Equivalent to `self[key]`, but provides a fallback default if the key is not found.
|
|
402
|
+
|
|
403
|
+
Args:
|
|
404
|
+
key: The key to look up in the cache.
|
|
405
|
+
default: The value to return if the key is not present in the cache. Defaults to None.
|
|
406
|
+
|
|
407
|
+
Returns:
|
|
408
|
+
The value associated with the key, or the default value if the key is not found.
|
|
409
|
+
"""
|
|
410
|
+
try:
|
|
411
|
+
return self._raw.get(key)
|
|
412
|
+
except _core.CoreKeyError:
|
|
413
|
+
return default # type: ignore[return-value]
|
|
414
|
+
|
|
415
|
+
def pop(self, key: KT, default: typing.Optional[DT] = None) -> typing.Union[VT, DT]:
|
|
416
|
+
"""
|
|
417
|
+
Removes specified key and return the corresponding value. If the key is not found, returns the `default`.
|
|
418
|
+
"""
|
|
419
|
+
try:
|
|
420
|
+
return self._raw.remove(key)
|
|
421
|
+
except _core.CoreKeyError:
|
|
422
|
+
return default # type: ignore[return-value] # type: ignore[return-value]
|
|
423
|
+
|
|
424
|
+
def setdefault(self, key: KT, default: typing.Optional[DT] = None) -> typing.Union[VT, DT]:
|
|
425
|
+
"""
|
|
426
|
+
Inserts key with a value of default if key is not in the cache.
|
|
427
|
+
|
|
428
|
+
Return the value for key if key is in the cache, else default.
|
|
429
|
+
"""
|
|
430
|
+
return self._raw.setdefault(key, default)
|
|
431
|
+
|
|
432
|
+
def popitem(self) -> typing.Tuple[KT, VT]:
|
|
433
|
+
"""Removes the element that has been in the cache the longest."""
|
|
434
|
+
try:
|
|
435
|
+
return self._raw.popitem()
|
|
436
|
+
except _core.CoreKeyError:
|
|
437
|
+
raise KeyError() from None
|
|
438
|
+
|
|
439
|
+
def drain(self, n: int) -> int: # pragma: no cover
|
|
440
|
+
"""Does the `popitem()` `n` times and returns count of removed items."""
|
|
441
|
+
if n <= 0:
|
|
442
|
+
return 0
|
|
443
|
+
|
|
444
|
+
for i in range(n):
|
|
445
|
+
try:
|
|
446
|
+
self._raw.popitem()
|
|
447
|
+
except _core.CoreKeyError:
|
|
448
|
+
return i
|
|
449
|
+
|
|
450
|
+
return i
|
|
451
|
+
|
|
452
|
+
def update(self, iterable: typing.Union[dict, typing.Iterable[tuple]]) -> None:
|
|
453
|
+
"""Updates the cache with elements from a dictionary or an iterable object of key/value pairs."""
|
|
454
|
+
if hasattr(iterable, "items"):
|
|
455
|
+
iterable = iterable.items()
|
|
456
|
+
|
|
457
|
+
self._raw.update(iterable)
|
|
458
|
+
|
|
459
|
+
def __setitem__(self, key: KT, value: VT) -> None:
|
|
460
|
+
self.insert(key, value)
|
|
461
|
+
|
|
462
|
+
def __getitem__(self, key: KT) -> VT:
|
|
463
|
+
try:
|
|
464
|
+
return self._raw.get(key)
|
|
465
|
+
except _core.CoreKeyError:
|
|
466
|
+
raise KeyError(key) from None
|
|
467
|
+
|
|
468
|
+
def __delitem__(self, key: KT) -> None:
|
|
469
|
+
try:
|
|
470
|
+
self._raw.remove(key)
|
|
471
|
+
except _core.CoreKeyError:
|
|
472
|
+
raise KeyError(key) from None
|
|
473
|
+
|
|
474
|
+
def __eq__(self, other) -> bool:
|
|
475
|
+
if not isinstance(other, FIFOCache):
|
|
476
|
+
return False # pragma: no cover
|
|
477
|
+
|
|
478
|
+
return self._raw == other._raw
|
|
479
|
+
|
|
480
|
+
def __ne__(self, other) -> bool:
|
|
481
|
+
if not isinstance(other, FIFOCache):
|
|
482
|
+
return False # pragma: no cover
|
|
483
|
+
|
|
484
|
+
return self._raw != other._raw
|
|
485
|
+
|
|
486
|
+
def shrink_to_fit(self) -> None:
|
|
487
|
+
"""Shrinks the cache to fit len(self) elements."""
|
|
488
|
+
self._raw.shrink_to_fit()
|
|
489
|
+
|
|
490
|
+
def clear(self, *, reuse: bool = False) -> None:
|
|
491
|
+
"""
|
|
492
|
+
Removes all items from cache.
|
|
493
|
+
|
|
494
|
+
If reuse is True, will not free the memory for reusing in the future.
|
|
495
|
+
"""
|
|
496
|
+
self._raw.clear(reuse)
|
|
497
|
+
|
|
498
|
+
def items(self) -> IteratorView[typing.Tuple[KT, VT]]:
|
|
499
|
+
"""
|
|
500
|
+
Returns an iterable object of the cache's items (key-value pairs).
|
|
501
|
+
|
|
502
|
+
Notes:
|
|
503
|
+
- You should not make any changes in cache while using this iterable object.
|
|
504
|
+
"""
|
|
505
|
+
return IteratorView(self._raw.items(), lambda x: x)
|
|
506
|
+
|
|
507
|
+
def keys(self) -> IteratorView[KT]:
|
|
508
|
+
"""
|
|
509
|
+
Returns an iterable object of the cache's keys.
|
|
510
|
+
|
|
511
|
+
Notes:
|
|
512
|
+
- You should not make any changes in cache while using this iterable object.
|
|
513
|
+
"""
|
|
514
|
+
return IteratorView(self._raw.items(), lambda x: x[0])
|
|
515
|
+
|
|
516
|
+
def values(self) -> IteratorView[VT]:
|
|
517
|
+
"""
|
|
518
|
+
Returns an iterable object of the cache's values.
|
|
519
|
+
|
|
520
|
+
Notes:
|
|
521
|
+
- You should not make any changes in cache while using this iterable object.
|
|
522
|
+
"""
|
|
523
|
+
return IteratorView(self._raw.items(), lambda x: x[1])
|
|
524
|
+
|
|
525
|
+
def first(self, n: int = 0) -> typing.Optional[KT]:
|
|
526
|
+
"""
|
|
527
|
+
Returns the first key in cache; this is the one which will be removed by `popitem()` (if n == 0).
|
|
528
|
+
|
|
529
|
+
By using `n` parameter, you can browse order index by index.
|
|
530
|
+
"""
|
|
531
|
+
if n < 0:
|
|
532
|
+
n = len(self._raw) + n
|
|
533
|
+
|
|
534
|
+
if n < 0:
|
|
535
|
+
return None
|
|
536
|
+
|
|
537
|
+
return self._raw.get_index(n)
|
|
538
|
+
|
|
539
|
+
def last(self) -> typing.Optional[KT]:
|
|
540
|
+
"""
|
|
541
|
+
Returns the last key in cache. Equals to `self.first(-1)`.
|
|
542
|
+
"""
|
|
543
|
+
return self._raw.get_index(len(self._raw) - 1)
|
|
544
|
+
|
|
545
|
+
def copy(self) -> "FIFOCache[KT, VT]":
|
|
546
|
+
"""Returns a shallow copy of the cache"""
|
|
547
|
+
return self.__copy__()
|
|
548
|
+
|
|
549
|
+
def __copy__(self) -> "FIFOCache[KT, VT]":
|
|
550
|
+
cls = type(self)
|
|
551
|
+
copied = cls.__new__(cls)
|
|
552
|
+
copied._raw = _std_copy.copy(self._raw)
|
|
553
|
+
return copied
|
|
554
|
+
|
|
555
|
+
def __deepcopy__(self, memo) -> "FIFOCache[KT, VT]":
|
|
556
|
+
cls = type(self)
|
|
557
|
+
copied = cls.__new__(cls)
|
|
558
|
+
copied._raw = _std_copy.deepcopy(self._raw, memo)
|
|
559
|
+
return copied
|
|
560
|
+
|
|
561
|
+
def __iter__(self) -> IteratorView[KT]:
|
|
562
|
+
return self.keys()
|
|
563
|
+
|
|
564
|
+
def __repr__(self) -> str:
|
|
565
|
+
cls = type(self)
|
|
566
|
+
|
|
567
|
+
return "%s.%s[%d/%d](%s)" % (
|
|
568
|
+
cls.__module__,
|
|
569
|
+
cls.__name__,
|
|
570
|
+
len(self._raw),
|
|
571
|
+
self._raw.maxsize(),
|
|
572
|
+
_items_to_str(self._raw.items(), len(self._raw)),
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
class RRCache(BaseCacheImpl[KT, VT]):
|
|
577
|
+
"""
|
|
578
|
+
A thread-safe cache implementation with Random Replacement (RR) policy.
|
|
579
|
+
|
|
580
|
+
This cache randomly selects and removes elements when the cache reaches its maximum size,
|
|
581
|
+
ensuring a simple and efficient caching mechanism with configurable capacity.
|
|
582
|
+
|
|
583
|
+
Supports operations like insertion, retrieval, deletion, and iteration.
|
|
584
|
+
"""
|
|
585
|
+
|
|
586
|
+
__slots__ = ("_raw",)
|
|
587
|
+
|
|
588
|
+
def __init__(
|
|
589
|
+
self,
|
|
590
|
+
maxsize: int,
|
|
591
|
+
iterable: typing.Union[typing.Union[dict, typing.Iterable[tuple]], None] = None,
|
|
592
|
+
*,
|
|
593
|
+
capacity: int = 0,
|
|
594
|
+
maxmemory: int = 0,
|
|
595
|
+
) -> None:
|
|
596
|
+
"""
|
|
597
|
+
Initialize a new RRCache instance.
|
|
598
|
+
|
|
599
|
+
Args:
|
|
600
|
+
maxsize (int): Maximum size of the cache. A value of zero means unlimited capacity.
|
|
601
|
+
iterable (dict or Iterable[tuple], optional): Initial data to populate the cache. Defaults to None.
|
|
602
|
+
capacity (int, optional): Preallocated capacity for the cache to minimize reallocations. Defaults to 0.
|
|
603
|
+
maxmemory (int, optional): Maximum memory (bytes) allowed for cached entries. Zero means unlimited.
|
|
604
|
+
When maxmemory is set, updates can evict any key, including the updated key.
|
|
605
|
+
On PyPy. In PyPy, the size of each object is assumed to be 1 if the object
|
|
606
|
+
does not have a `__sizeof__` method.
|
|
607
|
+
|
|
608
|
+
Note:
|
|
609
|
+
- The cache size limit is immutable after initialization.
|
|
610
|
+
- If an iterable is provided, the cache will be populated using the update method.
|
|
611
|
+
"""
|
|
612
|
+
self._raw = _core.RRCache(maxsize, capacity=capacity, maxmemory=maxmemory)
|
|
613
|
+
|
|
614
|
+
if iterable is not None:
|
|
615
|
+
self.update(iterable)
|
|
616
|
+
|
|
617
|
+
@property
|
|
618
|
+
def maxsize(self) -> int:
|
|
619
|
+
return self._raw.maxsize()
|
|
620
|
+
|
|
621
|
+
@property
|
|
622
|
+
def maxmemory(self) -> int:
|
|
623
|
+
return self._raw.maxmemory()
|
|
624
|
+
|
|
625
|
+
def capacity(self) -> int:
|
|
626
|
+
"""Returns the number of elements the map can hold without reallocating."""
|
|
627
|
+
return self._raw.capacity()
|
|
628
|
+
|
|
629
|
+
def memory(self) -> int:
|
|
630
|
+
"""Returns the total estimated memory usage of cached entries in bytes."""
|
|
631
|
+
return self._raw.memory()
|
|
632
|
+
|
|
633
|
+
def __len__(self) -> int:
|
|
634
|
+
return len(self._raw)
|
|
635
|
+
|
|
636
|
+
def __sizeof__(self): # pragma: no cover
|
|
637
|
+
return self._raw.__sizeof__()
|
|
638
|
+
|
|
639
|
+
def __contains__(self, key: KT) -> bool:
|
|
640
|
+
return key in self._raw
|
|
641
|
+
|
|
642
|
+
def __bool__(self) -> bool:
|
|
643
|
+
return not self.is_empty()
|
|
644
|
+
|
|
645
|
+
def is_empty(self) -> bool:
|
|
646
|
+
return self._raw.is_empty()
|
|
647
|
+
|
|
648
|
+
def is_full(self) -> bool:
|
|
649
|
+
return self._raw.is_full()
|
|
650
|
+
|
|
651
|
+
def insert(self, key: KT, value: VT) -> typing.Optional[VT]:
|
|
652
|
+
"""
|
|
653
|
+
Inserts a key-value pair into the cache, returning the previous value if the key existed.
|
|
654
|
+
|
|
655
|
+
Equivalent to `self[key] = value`, but with additional return value semantics:
|
|
656
|
+
|
|
657
|
+
- If the key was not previously in the cache, returns None.
|
|
658
|
+
- If the key was already present, updates the value and returns the old value.
|
|
659
|
+
The key itself is not modified.
|
|
660
|
+
|
|
661
|
+
Args:
|
|
662
|
+
key: The key to insert.
|
|
663
|
+
value: The value to associate with the key.
|
|
664
|
+
|
|
665
|
+
Returns:
|
|
666
|
+
The previous value associated with the key, or None if the key was not present.
|
|
667
|
+
"""
|
|
668
|
+
return self._raw.insert(key, value)
|
|
669
|
+
|
|
670
|
+
def get(self, key: KT, default: typing.Optional[DT] = None) -> typing.Union[VT, DT]:
|
|
671
|
+
"""
|
|
672
|
+
Retrieves the value for a given key from the cache.
|
|
673
|
+
|
|
674
|
+
Returns the value associated with the key if present, otherwise returns the specified default value.
|
|
675
|
+
Equivalent to `self[key]`, but provides a fallback default if the key is not found.
|
|
676
|
+
|
|
677
|
+
Args:
|
|
678
|
+
key: The key to look up in the cache.
|
|
679
|
+
default: The value to return if the key is not present in the cache. Defaults to None.
|
|
680
|
+
|
|
681
|
+
Returns:
|
|
682
|
+
The value associated with the key, or the default value if the key is not found.
|
|
683
|
+
"""
|
|
684
|
+
try:
|
|
685
|
+
return self._raw.get(key)
|
|
686
|
+
except _core.CoreKeyError:
|
|
687
|
+
return default # type: ignore[return-value]
|
|
688
|
+
|
|
689
|
+
def pop(self, key: KT, default: typing.Optional[DT] = None) -> typing.Union[VT, DT]:
|
|
690
|
+
"""
|
|
691
|
+
Removes specified key and return the corresponding value. If the key is not found, returns the `default`.
|
|
692
|
+
"""
|
|
693
|
+
try:
|
|
694
|
+
return self._raw.remove(key)
|
|
695
|
+
except _core.CoreKeyError:
|
|
696
|
+
return default # type: ignore[return-value]
|
|
697
|
+
|
|
698
|
+
def setdefault(self, key: KT, default: typing.Optional[DT] = None) -> typing.Union[VT, DT]:
|
|
699
|
+
"""
|
|
700
|
+
Inserts key with a value of default if key is not in the cache.
|
|
701
|
+
|
|
702
|
+
Return the value for key if key is in the cache, else default.
|
|
703
|
+
"""
|
|
704
|
+
return self._raw.setdefault(key, default)
|
|
705
|
+
|
|
706
|
+
def popitem(self) -> typing.Tuple[KT, VT]:
|
|
707
|
+
"""Randomly selects and removes a (key, value) pair from the cache."""
|
|
708
|
+
try:
|
|
709
|
+
return self._raw.popitem()
|
|
710
|
+
except _core.CoreKeyError:
|
|
711
|
+
raise KeyError() from None
|
|
712
|
+
|
|
713
|
+
def drain(self, n: int) -> int: # pragma: no cover
|
|
714
|
+
"""Does the `popitem()` `n` times and returns count of removed items."""
|
|
715
|
+
if n <= 0:
|
|
716
|
+
return 0
|
|
717
|
+
|
|
718
|
+
for i in range(n):
|
|
719
|
+
try:
|
|
720
|
+
self._raw.popitem()
|
|
721
|
+
except _core.CoreKeyError:
|
|
722
|
+
return i
|
|
723
|
+
|
|
724
|
+
return i
|
|
725
|
+
|
|
726
|
+
def update(self, iterable: typing.Union[dict, typing.Iterable[tuple]]) -> None:
|
|
727
|
+
"""Updates the cache with elements from a dictionary or an iterable object of key/value pairs."""
|
|
728
|
+
if hasattr(iterable, "items"):
|
|
729
|
+
iterable = iterable.items()
|
|
730
|
+
|
|
731
|
+
self._raw.update(iterable)
|
|
732
|
+
|
|
733
|
+
def random_key(self) -> KT:
|
|
734
|
+
"""
|
|
735
|
+
Randomly selects and returns a key from the cache.
|
|
736
|
+
Raises `KeyError` If the cache is empty.
|
|
737
|
+
"""
|
|
738
|
+
try:
|
|
739
|
+
return self._raw.random_key()
|
|
740
|
+
except _core.CoreKeyError:
|
|
741
|
+
raise KeyError() from None
|
|
742
|
+
|
|
743
|
+
def __setitem__(self, key: KT, value: VT) -> None:
|
|
744
|
+
self.insert(key, value)
|
|
745
|
+
|
|
746
|
+
def __getitem__(self, key: KT) -> VT:
|
|
747
|
+
try:
|
|
748
|
+
return self._raw.get(key)
|
|
749
|
+
except _core.CoreKeyError:
|
|
750
|
+
raise KeyError(key) from None
|
|
751
|
+
|
|
752
|
+
def __delitem__(self, key: KT) -> None:
|
|
753
|
+
try:
|
|
754
|
+
self._raw.remove(key)
|
|
755
|
+
except _core.CoreKeyError:
|
|
756
|
+
raise KeyError(key) from None
|
|
757
|
+
|
|
758
|
+
def __eq__(self, other) -> bool:
|
|
759
|
+
if not isinstance(other, RRCache):
|
|
760
|
+
return False # pragma: no cover
|
|
761
|
+
|
|
762
|
+
return self._raw == other._raw
|
|
763
|
+
|
|
764
|
+
def __ne__(self, other) -> bool:
|
|
765
|
+
if not isinstance(other, RRCache):
|
|
766
|
+
return False # pragma: no cover
|
|
767
|
+
|
|
768
|
+
return self._raw != other._raw
|
|
769
|
+
|
|
770
|
+
def shrink_to_fit(self) -> None:
|
|
771
|
+
"""Shrinks the cache to fit len(self) elements."""
|
|
772
|
+
self._raw.shrink_to_fit()
|
|
773
|
+
|
|
774
|
+
def clear(self, *, reuse: bool = False) -> None:
|
|
775
|
+
"""
|
|
776
|
+
Removes all items from cache.
|
|
777
|
+
|
|
778
|
+
If reuse is True, will not free the memory for reusing in the future.
|
|
779
|
+
"""
|
|
780
|
+
self._raw.clear(reuse)
|
|
781
|
+
|
|
782
|
+
def items(self) -> IteratorView[typing.Tuple[KT, VT]]:
|
|
783
|
+
"""
|
|
784
|
+
Returns an iterable object of the cache's items (key-value pairs).
|
|
785
|
+
|
|
786
|
+
Notes:
|
|
787
|
+
- You should not make any changes in cache while using this iterable object.
|
|
788
|
+
- Items are not ordered.
|
|
789
|
+
"""
|
|
790
|
+
return IteratorView(self._raw.items(), lambda x: x)
|
|
791
|
+
|
|
792
|
+
def keys(self) -> IteratorView[KT]:
|
|
793
|
+
"""
|
|
794
|
+
Returns an iterable object of the cache's keys.
|
|
795
|
+
|
|
796
|
+
Notes:
|
|
797
|
+
- You should not make any changes in cache while using this iterable object.
|
|
798
|
+
- Keys are not ordered.
|
|
799
|
+
"""
|
|
800
|
+
return IteratorView(self._raw.items(), lambda x: x[0])
|
|
801
|
+
|
|
802
|
+
def values(self) -> IteratorView[VT]:
|
|
803
|
+
"""
|
|
804
|
+
Returns an iterable object of the cache's values.
|
|
805
|
+
|
|
806
|
+
Notes:
|
|
807
|
+
- You should not make any changes in cache while using this iterable object.
|
|
808
|
+
- Values are not ordered.
|
|
809
|
+
"""
|
|
810
|
+
return IteratorView(self._raw.items(), lambda x: x[1])
|
|
811
|
+
|
|
812
|
+
def copy(self) -> "RRCache[KT, VT]":
|
|
813
|
+
"""Returns a shallow copy of the cache"""
|
|
814
|
+
return self.__copy__()
|
|
815
|
+
|
|
816
|
+
def __copy__(self) -> "RRCache[KT, VT]":
|
|
817
|
+
cls = type(self)
|
|
818
|
+
copied = cls.__new__(cls)
|
|
819
|
+
copied._raw = _std_copy.copy(self._raw)
|
|
820
|
+
return copied
|
|
821
|
+
|
|
822
|
+
def __deepcopy__(self, memo) -> "RRCache[KT, VT]":
|
|
823
|
+
cls = type(self)
|
|
824
|
+
copied = cls.__new__(cls)
|
|
825
|
+
copied._raw = _std_copy.deepcopy(self._raw, memo)
|
|
826
|
+
return copied
|
|
827
|
+
|
|
828
|
+
def __iter__(self) -> IteratorView[KT]:
|
|
829
|
+
return self.keys()
|
|
830
|
+
|
|
831
|
+
def __repr__(self) -> str:
|
|
832
|
+
cls = type(self)
|
|
833
|
+
|
|
834
|
+
return "%s.%s[%d/%d](%s)" % (
|
|
835
|
+
cls.__module__,
|
|
836
|
+
cls.__name__,
|
|
837
|
+
len(self._raw),
|
|
838
|
+
self._raw.maxsize(),
|
|
839
|
+
_items_to_str(self._raw.items(), len(self._raw)),
|
|
840
|
+
)
|
|
841
|
+
|
|
842
|
+
|
|
843
|
+
class LRUCache(BaseCacheImpl[KT, VT]):
|
|
844
|
+
"""
|
|
845
|
+
Thread-safe Least Recently Used (LRU) cache implementation.
|
|
846
|
+
|
|
847
|
+
Provides a cache that automatically removes the least recently used items when
|
|
848
|
+
the cache reaches its maximum size. Supports various operations like insertion,
|
|
849
|
+
retrieval, and management of cached items with configurable maximum size and
|
|
850
|
+
initial capacity.
|
|
851
|
+
|
|
852
|
+
Key features:
|
|
853
|
+
- Configurable maximum cache size
|
|
854
|
+
- Optional initial capacity allocation
|
|
855
|
+
- Thread-safe operations
|
|
856
|
+
- Efficient key-value pair management
|
|
857
|
+
- Supports initialization from dictionaries or iterables
|
|
858
|
+
"""
|
|
859
|
+
|
|
860
|
+
__slots__ = ("_raw",)
|
|
861
|
+
|
|
862
|
+
def __init__(
|
|
863
|
+
self,
|
|
864
|
+
maxsize: int,
|
|
865
|
+
iterable: typing.Union[typing.Union[dict, typing.Iterable[tuple]], None] = None,
|
|
866
|
+
*,
|
|
867
|
+
capacity: int = 0,
|
|
868
|
+
maxmemory: int = 0,
|
|
869
|
+
) -> None:
|
|
870
|
+
"""
|
|
871
|
+
Initialize a new LRU Cache instance.
|
|
872
|
+
|
|
873
|
+
Args:
|
|
874
|
+
maxsize (int): Maximum size of the cache. Zero indicates unlimited size.
|
|
875
|
+
iterable (dict | Iterable[tuple], optional): Initial data to populate the cache.
|
|
876
|
+
capacity (int, optional): Pre-allocated capacity for the cache to minimize reallocations.
|
|
877
|
+
maxmemory (int, optional): Maximum memory (bytes) allowed for cached entries. Zero means unlimited.
|
|
878
|
+
On PyPy. In PyPy, the size of each object is assumed to be 1 if the object
|
|
879
|
+
does not have a `__sizeof__` method.
|
|
880
|
+
|
|
881
|
+
Notes:
|
|
882
|
+
- The cache size is immutable after initialization.
|
|
883
|
+
- If an iterable is provided, it will be used to populate the cache.
|
|
884
|
+
"""
|
|
885
|
+
self._raw = _core.LRUCache(maxsize, capacity=capacity, maxmemory=maxmemory)
|
|
886
|
+
|
|
887
|
+
if iterable is not None:
|
|
888
|
+
self.update(iterable)
|
|
889
|
+
|
|
890
|
+
@property
|
|
891
|
+
def maxsize(self) -> int:
|
|
892
|
+
return self._raw.maxsize()
|
|
893
|
+
|
|
894
|
+
@property
|
|
895
|
+
def maxmemory(self) -> int:
|
|
896
|
+
return self._raw.maxmemory()
|
|
897
|
+
|
|
898
|
+
def capacity(self) -> int:
|
|
899
|
+
"""Returns the number of elements the map can hold without reallocating."""
|
|
900
|
+
return self._raw.capacity()
|
|
901
|
+
|
|
902
|
+
def memory(self) -> int:
|
|
903
|
+
"""Returns the total estimated memory usage of cached entries in bytes."""
|
|
904
|
+
return self._raw.memory()
|
|
905
|
+
|
|
906
|
+
def __len__(self) -> int:
|
|
907
|
+
return len(self._raw)
|
|
908
|
+
|
|
909
|
+
def __sizeof__(self): # pragma: no cover
|
|
910
|
+
return self._raw.__sizeof__()
|
|
911
|
+
|
|
912
|
+
def __contains__(self, key: KT) -> bool:
|
|
913
|
+
return key in self._raw
|
|
914
|
+
|
|
915
|
+
def __bool__(self) -> bool:
|
|
916
|
+
return not self.is_empty()
|
|
917
|
+
|
|
918
|
+
def is_empty(self) -> bool:
|
|
919
|
+
return self._raw.is_empty()
|
|
920
|
+
|
|
921
|
+
def is_full(self) -> bool:
|
|
922
|
+
return self._raw.is_full()
|
|
923
|
+
|
|
924
|
+
def insert(self, key: KT, value: VT) -> typing.Optional[VT]:
|
|
925
|
+
"""
|
|
926
|
+
Inserts a key-value pair into the cache, returning the previous value if the key existed.
|
|
927
|
+
|
|
928
|
+
Equivalent to `self[key] = value`, but with additional return value semantics:
|
|
929
|
+
|
|
930
|
+
- If the key was not previously in the cache, returns None.
|
|
931
|
+
- If the key was already present, updates the value and returns the old value.
|
|
932
|
+
The key itself is not modified.
|
|
933
|
+
|
|
934
|
+
Args:
|
|
935
|
+
key: The key to insert.
|
|
936
|
+
value: The value to associate with the key.
|
|
937
|
+
|
|
938
|
+
Returns:
|
|
939
|
+
The previous value associated with the key, or None if the key was not present.
|
|
940
|
+
"""
|
|
941
|
+
return self._raw.insert(key, value)
|
|
942
|
+
|
|
943
|
+
def peek(self, key: KT, default: typing.Optional[DT] = None) -> typing.Union[VT, DT]:
|
|
944
|
+
"""
|
|
945
|
+
Searches for a key-value in the cache and returns it (without moving the key to recently used).
|
|
946
|
+
"""
|
|
947
|
+
try:
|
|
948
|
+
return self._raw.peek(key)
|
|
949
|
+
except _core.CoreKeyError:
|
|
950
|
+
return default # type: ignore[return-value]
|
|
951
|
+
|
|
952
|
+
def get(self, key: KT, default: typing.Optional[DT] = None) -> typing.Union[VT, DT]:
|
|
953
|
+
"""
|
|
954
|
+
Retrieves the value for a given key from the cache.
|
|
955
|
+
|
|
956
|
+
Returns the value associated with the key if present, otherwise returns the specified default value.
|
|
957
|
+
Equivalent to `self[key]`, but provides a fallback default if the key is not found.
|
|
958
|
+
|
|
959
|
+
Args:
|
|
960
|
+
key: The key to look up in the cache.
|
|
961
|
+
default: The value to return if the key is not present in the cache. Defaults to None.
|
|
962
|
+
|
|
963
|
+
Returns:
|
|
964
|
+
The value associated with the key, or the default value if the key is not found.
|
|
965
|
+
"""
|
|
966
|
+
try:
|
|
967
|
+
return self._raw.get(key)
|
|
968
|
+
except _core.CoreKeyError:
|
|
969
|
+
return default # type: ignore[return-value]
|
|
970
|
+
|
|
971
|
+
def pop(self, key: KT, default: typing.Optional[DT] = None) -> typing.Union[VT, DT]:
|
|
972
|
+
"""
|
|
973
|
+
Removes specified key and return the corresponding value. If the key is not found, returns the `default`.
|
|
974
|
+
"""
|
|
975
|
+
try:
|
|
976
|
+
return self._raw.remove(key)
|
|
977
|
+
except _core.CoreKeyError:
|
|
978
|
+
return default # type: ignore[return-value]
|
|
979
|
+
|
|
980
|
+
def setdefault(self, key: KT, default: typing.Optional[DT] = None) -> typing.Union[VT, DT]:
|
|
981
|
+
"""
|
|
982
|
+
Inserts key with a value of default if key is not in the cache.
|
|
983
|
+
|
|
984
|
+
Return the value for key if key is in the cache, else default.
|
|
985
|
+
"""
|
|
986
|
+
return self._raw.setdefault(key, default)
|
|
987
|
+
|
|
988
|
+
def popitem(self) -> typing.Tuple[KT, VT]:
|
|
989
|
+
"""
|
|
990
|
+
Removes the least recently used item from the cache and returns it as a (key, value) tuple.
|
|
991
|
+
Raises KeyError if the cache is empty.
|
|
992
|
+
"""
|
|
993
|
+
try:
|
|
994
|
+
return self._raw.popitem()
|
|
995
|
+
except _core.CoreKeyError: # pragma: no cover
|
|
996
|
+
raise KeyError() from None
|
|
997
|
+
|
|
998
|
+
def drain(self, n: int) -> int: # pragma: no cover
|
|
999
|
+
"""Does the `popitem()` `n` times and returns count of removed items."""
|
|
1000
|
+
if n <= 0:
|
|
1001
|
+
return 0
|
|
1002
|
+
|
|
1003
|
+
for i in range(n):
|
|
1004
|
+
try:
|
|
1005
|
+
self._raw.popitem()
|
|
1006
|
+
except _core.CoreKeyError:
|
|
1007
|
+
return i
|
|
1008
|
+
|
|
1009
|
+
return i
|
|
1010
|
+
|
|
1011
|
+
def update(self, iterable: typing.Union[dict, typing.Iterable[tuple]]) -> None:
|
|
1012
|
+
"""Updates the cache with elements from a dictionary or an iterable object of key/value pairs."""
|
|
1013
|
+
if hasattr(iterable, "items"):
|
|
1014
|
+
iterable = iterable.items()
|
|
1015
|
+
|
|
1016
|
+
self._raw.update(iterable)
|
|
1017
|
+
|
|
1018
|
+
def __setitem__(self, key: KT, value: VT) -> None:
|
|
1019
|
+
self.insert(key, value)
|
|
1020
|
+
|
|
1021
|
+
def __getitem__(self, key: KT) -> VT:
|
|
1022
|
+
try:
|
|
1023
|
+
return self._raw.get(key)
|
|
1024
|
+
except _core.CoreKeyError:
|
|
1025
|
+
raise KeyError(key) from None
|
|
1026
|
+
|
|
1027
|
+
def __delitem__(self, key: KT) -> None:
|
|
1028
|
+
try:
|
|
1029
|
+
self._raw.remove(key)
|
|
1030
|
+
except _core.CoreKeyError:
|
|
1031
|
+
raise KeyError(key) from None
|
|
1032
|
+
|
|
1033
|
+
def __eq__(self, other) -> bool:
|
|
1034
|
+
if not isinstance(other, LRUCache):
|
|
1035
|
+
return False # pragma: no cover
|
|
1036
|
+
|
|
1037
|
+
return self._raw == other._raw
|
|
1038
|
+
|
|
1039
|
+
def __ne__(self, other) -> bool:
|
|
1040
|
+
if not isinstance(other, LRUCache):
|
|
1041
|
+
return False # pragma: no cover
|
|
1042
|
+
|
|
1043
|
+
return self._raw != other._raw
|
|
1044
|
+
|
|
1045
|
+
def shrink_to_fit(self) -> None:
|
|
1046
|
+
"""Shrinks the cache to fit len(self) elements."""
|
|
1047
|
+
self._raw.shrink_to_fit()
|
|
1048
|
+
|
|
1049
|
+
def clear(self, *, reuse: bool = False) -> None:
|
|
1050
|
+
"""
|
|
1051
|
+
Removes all items from cache.
|
|
1052
|
+
|
|
1053
|
+
If reuse is True, will not free the memory for reusing in the future.
|
|
1054
|
+
"""
|
|
1055
|
+
self._raw.clear(reuse)
|
|
1056
|
+
|
|
1057
|
+
def items(self) -> IteratorView[typing.Tuple[KT, VT]]:
|
|
1058
|
+
"""
|
|
1059
|
+
Returns an iterable object of the cache's items (key-value pairs).
|
|
1060
|
+
|
|
1061
|
+
Notes:
|
|
1062
|
+
- You should not make any changes in cache while using this iterable object.
|
|
1063
|
+
"""
|
|
1064
|
+
return IteratorView(self._raw.items(), lambda x: x)
|
|
1065
|
+
|
|
1066
|
+
def keys(self) -> IteratorView[KT]:
|
|
1067
|
+
"""
|
|
1068
|
+
Returns an iterable object of the cache's keys.
|
|
1069
|
+
|
|
1070
|
+
Notes:
|
|
1071
|
+
- You should not make any changes in cache while using this iterable object.
|
|
1072
|
+
"""
|
|
1073
|
+
return IteratorView(self._raw.items(), lambda x: x[0])
|
|
1074
|
+
|
|
1075
|
+
def values(self) -> IteratorView[VT]:
|
|
1076
|
+
"""
|
|
1077
|
+
Returns an iterable object of the cache's values.
|
|
1078
|
+
|
|
1079
|
+
Notes:
|
|
1080
|
+
- You should not make any changes in cache while using this iterable object.
|
|
1081
|
+
"""
|
|
1082
|
+
return IteratorView(self._raw.items(), lambda x: x[1])
|
|
1083
|
+
|
|
1084
|
+
def least_recently_used(self) -> typing.Optional[KT]:
|
|
1085
|
+
"""
|
|
1086
|
+
Returns the key in the cache that has not been accessed in the longest time.
|
|
1087
|
+
"""
|
|
1088
|
+
return self._raw.least_recently_used()
|
|
1089
|
+
|
|
1090
|
+
def most_recently_used(self) -> typing.Optional[KT]:
|
|
1091
|
+
"""
|
|
1092
|
+
Returns the key in the cache that has been accessed in the shortest time.
|
|
1093
|
+
"""
|
|
1094
|
+
return self._raw.most_recently_used()
|
|
1095
|
+
|
|
1096
|
+
def copy(self) -> "LRUCache[KT, VT]":
|
|
1097
|
+
"""Returns a shallow copy of the cache"""
|
|
1098
|
+
return self.__copy__()
|
|
1099
|
+
|
|
1100
|
+
def __copy__(self) -> "LRUCache[KT, VT]":
|
|
1101
|
+
cls = type(self)
|
|
1102
|
+
copied = cls.__new__(cls)
|
|
1103
|
+
copied._raw = _std_copy.copy(self._raw)
|
|
1104
|
+
return copied
|
|
1105
|
+
|
|
1106
|
+
def __deepcopy__(self, memo) -> "LRUCache[KT, VT]":
|
|
1107
|
+
cls = type(self)
|
|
1108
|
+
copied = cls.__new__(cls)
|
|
1109
|
+
copied._raw = _std_copy.deepcopy(self._raw, memo)
|
|
1110
|
+
return copied
|
|
1111
|
+
|
|
1112
|
+
def __iter__(self) -> IteratorView[KT]:
|
|
1113
|
+
return self.keys()
|
|
1114
|
+
|
|
1115
|
+
def __repr__(self) -> str:
|
|
1116
|
+
cls = type(self)
|
|
1117
|
+
|
|
1118
|
+
return "%s.%s[%d/%d](%s)" % (
|
|
1119
|
+
cls.__module__,
|
|
1120
|
+
cls.__name__,
|
|
1121
|
+
len(self._raw),
|
|
1122
|
+
self._raw.maxsize(),
|
|
1123
|
+
_items_to_str(self._raw.items(), len(self._raw)),
|
|
1124
|
+
)
|
|
1125
|
+
|
|
1126
|
+
|
|
1127
|
+
class LFUCache(BaseCacheImpl[KT, VT]):
|
|
1128
|
+
"""
|
|
1129
|
+
A thread-safe Least Frequently Used (LFU) cache implementation.
|
|
1130
|
+
|
|
1131
|
+
This cache removes elements that have been accessed the least number of times,
|
|
1132
|
+
regardless of their access time. It provides methods for inserting, retrieving,
|
|
1133
|
+
and managing cache entries with configurable maximum size and initial capacity.
|
|
1134
|
+
|
|
1135
|
+
Key features:
|
|
1136
|
+
- Thread-safe cache with LFU eviction policy
|
|
1137
|
+
- Configurable maximum size and initial capacity
|
|
1138
|
+
- Supports initialization from dictionaries or iterables
|
|
1139
|
+
- Provides methods for key-value management similar to dict
|
|
1140
|
+
"""
|
|
1141
|
+
|
|
1142
|
+
__slots__ = ("_raw",)
|
|
1143
|
+
|
|
1144
|
+
def __init__(
|
|
1145
|
+
self,
|
|
1146
|
+
maxsize: int,
|
|
1147
|
+
iterable: typing.Union[typing.Union[dict, typing.Iterable[tuple]], None] = None,
|
|
1148
|
+
*,
|
|
1149
|
+
capacity: int = 0,
|
|
1150
|
+
maxmemory: int = 0,
|
|
1151
|
+
) -> None:
|
|
1152
|
+
"""
|
|
1153
|
+
Initialize a new Least Frequently Used (LFU) cache.
|
|
1154
|
+
|
|
1155
|
+
Args:
|
|
1156
|
+
maxsize (int): Maximum size of the cache. A value of zero means unlimited size.
|
|
1157
|
+
iterable (dict or Iterable[tuple], optional): Initial data to populate the cache.
|
|
1158
|
+
capacity (int, optional): Initial hash table capacity to minimize reallocations. Defaults to 0.
|
|
1159
|
+
maxmemory (int, optional): Maximum memory (bytes) allowed for cached entries. Zero means unlimited.
|
|
1160
|
+
On PyPy. In PyPy, the size of each object is assumed to be 1 if the object
|
|
1161
|
+
does not have a `__sizeof__` method.
|
|
1162
|
+
|
|
1163
|
+
The cache uses a thread-safe LFU eviction policy, removing least frequently accessed items when the cache reaches its maximum size.
|
|
1164
|
+
"""
|
|
1165
|
+
self._raw = _core.LFUCache(maxsize, capacity=capacity, maxmemory=maxmemory)
|
|
1166
|
+
|
|
1167
|
+
if iterable is not None:
|
|
1168
|
+
self.update(iterable)
|
|
1169
|
+
|
|
1170
|
+
@property
|
|
1171
|
+
def maxsize(self) -> int:
|
|
1172
|
+
return self._raw.maxsize()
|
|
1173
|
+
|
|
1174
|
+
@property
|
|
1175
|
+
def maxmemory(self) -> int:
|
|
1176
|
+
return self._raw.maxmemory()
|
|
1177
|
+
|
|
1178
|
+
def capacity(self) -> int:
|
|
1179
|
+
"""Returns the number of elements the map can hold without reallocating."""
|
|
1180
|
+
return self._raw.capacity()
|
|
1181
|
+
|
|
1182
|
+
def memory(self) -> int:
|
|
1183
|
+
"""Returns the total estimated memory usage of cached entries in bytes."""
|
|
1184
|
+
return self._raw.memory()
|
|
1185
|
+
|
|
1186
|
+
def __len__(self) -> int:
|
|
1187
|
+
return len(self._raw)
|
|
1188
|
+
|
|
1189
|
+
def __sizeof__(self): # pragma: no cover
|
|
1190
|
+
return self._raw.__sizeof__()
|
|
1191
|
+
|
|
1192
|
+
def __contains__(self, key: KT) -> bool:
|
|
1193
|
+
return key in self._raw
|
|
1194
|
+
|
|
1195
|
+
def __bool__(self) -> bool:
|
|
1196
|
+
return not self.is_empty()
|
|
1197
|
+
|
|
1198
|
+
def is_empty(self) -> bool:
|
|
1199
|
+
return self._raw.is_empty()
|
|
1200
|
+
|
|
1201
|
+
def is_full(self) -> bool:
|
|
1202
|
+
return self._raw.is_full()
|
|
1203
|
+
|
|
1204
|
+
def insert(self, key: KT, value: VT) -> typing.Optional[VT]:
|
|
1205
|
+
"""
|
|
1206
|
+
Inserts a key-value pair into the cache, returning the previous value if the key existed.
|
|
1207
|
+
|
|
1208
|
+
Equivalent to `self[key] = value`, but with additional return value semantics:
|
|
1209
|
+
|
|
1210
|
+
- If the key was not previously in the cache, returns None.
|
|
1211
|
+
- If the key was already present, updates the value and returns the old value.
|
|
1212
|
+
The key itself is not modified.
|
|
1213
|
+
|
|
1214
|
+
Args:
|
|
1215
|
+
key: The key to insert.
|
|
1216
|
+
value: The value to associate with the key.
|
|
1217
|
+
|
|
1218
|
+
Returns:
|
|
1219
|
+
The previous value associated with the key, or None if the key was not present.
|
|
1220
|
+
"""
|
|
1221
|
+
return self._raw.insert(key, value)
|
|
1222
|
+
|
|
1223
|
+
def peek(
|
|
1224
|
+
self, key: KT, default: typing.Optional[DT] = None
|
|
1225
|
+
) -> typing.Union[VT, DT]: # pragma: no cover
|
|
1226
|
+
"""
|
|
1227
|
+
Searches for a key-value in the cache and returns it (without moving the key to recently used).
|
|
1228
|
+
"""
|
|
1229
|
+
try:
|
|
1230
|
+
return self._raw.peek(key)
|
|
1231
|
+
except _core.CoreKeyError:
|
|
1232
|
+
return default # type: ignore[return-value]
|
|
1233
|
+
|
|
1234
|
+
def get(self, key: KT, default: typing.Optional[DT] = None) -> typing.Union[VT, DT]:
|
|
1235
|
+
"""
|
|
1236
|
+
Retrieves the value for a given key from the cache.
|
|
1237
|
+
|
|
1238
|
+
Returns the value associated with the key if present, otherwise returns the specified default value.
|
|
1239
|
+
Equivalent to `self[key]`, but provides a fallback default if the key is not found.
|
|
1240
|
+
|
|
1241
|
+
Args:
|
|
1242
|
+
key: The key to look up in the cache.
|
|
1243
|
+
default: The value to return if the key is not present in the cache. Defaults to None.
|
|
1244
|
+
|
|
1245
|
+
Returns:
|
|
1246
|
+
The value associated with the key, or the default value if the key is not found.
|
|
1247
|
+
"""
|
|
1248
|
+
try:
|
|
1249
|
+
return self._raw.get(key)
|
|
1250
|
+
except _core.CoreKeyError:
|
|
1251
|
+
return default # type: ignore[return-value]
|
|
1252
|
+
|
|
1253
|
+
def pop(self, key: KT, default: typing.Optional[DT] = None) -> typing.Union[VT, DT]:
|
|
1254
|
+
"""
|
|
1255
|
+
Removes specified key and return the corresponding value. If the key is not found, returns the `default`.
|
|
1256
|
+
"""
|
|
1257
|
+
try:
|
|
1258
|
+
return self._raw.remove(key)
|
|
1259
|
+
except _core.CoreKeyError:
|
|
1260
|
+
return default # type: ignore[return-value]
|
|
1261
|
+
|
|
1262
|
+
def setdefault(self, key: KT, default: typing.Optional[DT] = None) -> typing.Union[VT, DT]:
|
|
1263
|
+
"""
|
|
1264
|
+
Inserts key with a value of default if key is not in the cache.
|
|
1265
|
+
|
|
1266
|
+
Return the value for key if key is in the cache, else default.
|
|
1267
|
+
"""
|
|
1268
|
+
return self._raw.setdefault(key, default)
|
|
1269
|
+
|
|
1270
|
+
def popitem(self) -> typing.Tuple[KT, VT]:
|
|
1271
|
+
"""
|
|
1272
|
+
Removes and returns the least frequently used (LFU) item from the cache.
|
|
1273
|
+
"""
|
|
1274
|
+
try:
|
|
1275
|
+
return self._raw.popitem()
|
|
1276
|
+
except _core.CoreKeyError: # pragma: no cover
|
|
1277
|
+
raise KeyError() from None
|
|
1278
|
+
|
|
1279
|
+
def drain(self, n: int) -> int: # pragma: no cover
|
|
1280
|
+
"""Does the `popitem()` `n` times and returns count of removed items."""
|
|
1281
|
+
if n <= 0:
|
|
1282
|
+
return 0
|
|
1283
|
+
|
|
1284
|
+
for i in range(n):
|
|
1285
|
+
try:
|
|
1286
|
+
self._raw.popitem()
|
|
1287
|
+
except _core.CoreKeyError:
|
|
1288
|
+
return i
|
|
1289
|
+
|
|
1290
|
+
return i
|
|
1291
|
+
|
|
1292
|
+
def update(self, iterable: typing.Union[dict, typing.Iterable[tuple]]) -> None:
|
|
1293
|
+
"""Updates the cache with elements from a dictionary or an iterable object of key/value pairs."""
|
|
1294
|
+
if hasattr(iterable, "items"):
|
|
1295
|
+
iterable = iterable.items()
|
|
1296
|
+
|
|
1297
|
+
self._raw.update(iterable)
|
|
1298
|
+
|
|
1299
|
+
def __setitem__(self, key: KT, value: VT) -> None:
|
|
1300
|
+
self.insert(key, value)
|
|
1301
|
+
|
|
1302
|
+
def __getitem__(self, key: KT) -> VT:
|
|
1303
|
+
try:
|
|
1304
|
+
return self._raw.get(key)
|
|
1305
|
+
except _core.CoreKeyError:
|
|
1306
|
+
raise KeyError(key) from None
|
|
1307
|
+
|
|
1308
|
+
def __delitem__(self, key: KT) -> None:
|
|
1309
|
+
try:
|
|
1310
|
+
self._raw.remove(key)
|
|
1311
|
+
except _core.CoreKeyError:
|
|
1312
|
+
raise KeyError(key) from None
|
|
1313
|
+
|
|
1314
|
+
def __eq__(self, other) -> bool:
|
|
1315
|
+
if not isinstance(other, LFUCache):
|
|
1316
|
+
return False # pragma: no cover
|
|
1317
|
+
|
|
1318
|
+
return self._raw == other._raw
|
|
1319
|
+
|
|
1320
|
+
def __ne__(self, other) -> bool:
|
|
1321
|
+
if not isinstance(other, LFUCache):
|
|
1322
|
+
return False # pragma: no cover
|
|
1323
|
+
|
|
1324
|
+
return self._raw != other._raw
|
|
1325
|
+
|
|
1326
|
+
def shrink_to_fit(self) -> None:
|
|
1327
|
+
"""Shrinks the cache to fit len(self) elements."""
|
|
1328
|
+
self._raw.shrink_to_fit()
|
|
1329
|
+
|
|
1330
|
+
def clear(self, *, reuse: bool = False) -> None:
|
|
1331
|
+
"""
|
|
1332
|
+
Removes all items from cache.
|
|
1333
|
+
|
|
1334
|
+
If reuse is True, will not free the memory for reusing in the future.
|
|
1335
|
+
"""
|
|
1336
|
+
self._raw.clear(reuse)
|
|
1337
|
+
|
|
1338
|
+
def items(self) -> IteratorView[typing.Tuple[KT, VT]]:
|
|
1339
|
+
"""
|
|
1340
|
+
Returns an iterable object of the cache's items (key-value pairs).
|
|
1341
|
+
|
|
1342
|
+
Notes:
|
|
1343
|
+
- You should not make any changes in cache while using this iterable object.
|
|
1344
|
+
"""
|
|
1345
|
+
return IteratorView(self._raw.items(), lambda x: (x[0], x[1]))
|
|
1346
|
+
|
|
1347
|
+
def items_with_frequency(self) -> IteratorView[typing.Tuple[KT, VT, int]]:
|
|
1348
|
+
"""
|
|
1349
|
+
Returns an iterable view - containing tuples of `(key, value, frequency)` - of the cache's items along with their access frequency.
|
|
1350
|
+
|
|
1351
|
+
Notes:
|
|
1352
|
+
- The returned iterator should not be used to modify the cache.
|
|
1353
|
+
- Frequency represents how many times the item has been accessed.
|
|
1354
|
+
"""
|
|
1355
|
+
return IteratorView(self._raw.items(), lambda x: x)
|
|
1356
|
+
|
|
1357
|
+
def keys(self) -> IteratorView[KT]:
|
|
1358
|
+
"""
|
|
1359
|
+
Returns an iterable object of the cache's keys.
|
|
1360
|
+
|
|
1361
|
+
Notes:
|
|
1362
|
+
- You should not make any changes in cache while using this iterable object.
|
|
1363
|
+
"""
|
|
1364
|
+
return IteratorView(self._raw.items(), lambda x: x[0])
|
|
1365
|
+
|
|
1366
|
+
def values(self) -> IteratorView[VT]:
|
|
1367
|
+
"""
|
|
1368
|
+
Returns an iterable object of the cache's values.
|
|
1369
|
+
|
|
1370
|
+
Notes:
|
|
1371
|
+
- You should not make any changes in cache while using this iterable object.
|
|
1372
|
+
"""
|
|
1373
|
+
return IteratorView(self._raw.items(), lambda x: x[1])
|
|
1374
|
+
|
|
1375
|
+
def least_frequently_used(self, n: int = 0) -> typing.Optional[KT]:
|
|
1376
|
+
"""
|
|
1377
|
+
Returns the key in the cache that has been accessed the least, regardless of time.
|
|
1378
|
+
|
|
1379
|
+
If n is given, returns the nth least frequently used key.
|
|
1380
|
+
|
|
1381
|
+
Notes:
|
|
1382
|
+
- This method may re-sort the cache which can cause iterators to be stopped.
|
|
1383
|
+
- Do not use this method while using iterators.
|
|
1384
|
+
"""
|
|
1385
|
+
if n < 0:
|
|
1386
|
+
n = len(self._raw) + n
|
|
1387
|
+
|
|
1388
|
+
if n < 0:
|
|
1389
|
+
return None
|
|
1390
|
+
|
|
1391
|
+
return self._raw.least_frequently_used(n)
|
|
1392
|
+
|
|
1393
|
+
def copy(self) -> "LFUCache[KT, VT]":
|
|
1394
|
+
"""Returns a shallow copy of the cache"""
|
|
1395
|
+
return self.__copy__()
|
|
1396
|
+
|
|
1397
|
+
def __copy__(self) -> "LFUCache[KT, VT]":
|
|
1398
|
+
cls = type(self)
|
|
1399
|
+
copied = cls.__new__(cls)
|
|
1400
|
+
copied._raw = _std_copy.copy(self._raw)
|
|
1401
|
+
return copied
|
|
1402
|
+
|
|
1403
|
+
def __deepcopy__(self, memo) -> "LFUCache[KT, VT]":
|
|
1404
|
+
cls = type(self)
|
|
1405
|
+
copied = cls.__new__(cls)
|
|
1406
|
+
copied._raw = _std_copy.deepcopy(self._raw, memo)
|
|
1407
|
+
return copied
|
|
1408
|
+
|
|
1409
|
+
def __iter__(self) -> IteratorView[KT]:
|
|
1410
|
+
return self.keys()
|
|
1411
|
+
|
|
1412
|
+
def __repr__(self) -> str:
|
|
1413
|
+
cls = type(self)
|
|
1414
|
+
|
|
1415
|
+
return "%s.%s[%d/%d](%s)" % (
|
|
1416
|
+
cls.__module__,
|
|
1417
|
+
cls.__name__,
|
|
1418
|
+
len(self._raw),
|
|
1419
|
+
self._raw.maxsize(),
|
|
1420
|
+
# NOTE: we cannot use self._raw.items() here because iterables a tuples of (key, value, frequency)
|
|
1421
|
+
_items_to_str(self.items(), len(self._raw)),
|
|
1422
|
+
)
|
|
1423
|
+
|
|
1424
|
+
|
|
1425
|
+
class TTLCache(BaseCacheImpl[KT, VT]):
|
|
1426
|
+
"""
|
|
1427
|
+
A thread-safe Time-To-Live (TTL) cache implementation with configurable maximum size and expiration.
|
|
1428
|
+
|
|
1429
|
+
This cache automatically removes elements that have expired based on their time-to-live setting.
|
|
1430
|
+
Supports various operations like insertion, retrieval, and iteration.
|
|
1431
|
+
"""
|
|
1432
|
+
|
|
1433
|
+
__slots__ = ("_raw",)
|
|
1434
|
+
|
|
1435
|
+
def __init__(
|
|
1436
|
+
self,
|
|
1437
|
+
maxsize: int,
|
|
1438
|
+
ttl: typing.Union[float, timedelta],
|
|
1439
|
+
iterable: typing.Union[typing.Union[dict, typing.Iterable[tuple]], None] = None,
|
|
1440
|
+
*,
|
|
1441
|
+
capacity: int = 0,
|
|
1442
|
+
maxmemory: int = 0,
|
|
1443
|
+
) -> None:
|
|
1444
|
+
"""
|
|
1445
|
+
Initialize a new TTL cache instance.
|
|
1446
|
+
|
|
1447
|
+
Args:
|
|
1448
|
+
maxsize: Maximum number of elements the cache can hold.
|
|
1449
|
+
ttl: Time-to-live for cache entries, either as seconds or a timedelta.
|
|
1450
|
+
iterable: Optional initial items to populate the cache, can be a dict or iterable of tuples.
|
|
1451
|
+
capacity: Optional initial capacity for the underlying cache storage. Defaults to 0.
|
|
1452
|
+
maxmemory: Maximum memory (bytes) allowed for cached entries. Zero means unlimited.
|
|
1453
|
+
On PyPy. In PyPy, the size of each object is assumed to be 1 if the object
|
|
1454
|
+
does not have a `__sizeof__` method.
|
|
1455
|
+
|
|
1456
|
+
Raises:
|
|
1457
|
+
ValueError: If the time-to-live (ttl) is not a positive number.
|
|
1458
|
+
"""
|
|
1459
|
+
if isinstance(ttl, timedelta):
|
|
1460
|
+
ttl = ttl.total_seconds()
|
|
1461
|
+
|
|
1462
|
+
if ttl <= 0:
|
|
1463
|
+
raise ValueError("ttl must be a positive number and non-zero")
|
|
1464
|
+
|
|
1465
|
+
self._raw = _core.TTLCache(maxsize, ttl, capacity=capacity, maxmemory=maxmemory)
|
|
1466
|
+
|
|
1467
|
+
if iterable is not None:
|
|
1468
|
+
self.update(iterable)
|
|
1469
|
+
|
|
1470
|
+
@property
|
|
1471
|
+
def maxsize(self) -> int:
|
|
1472
|
+
return self._raw.maxsize()
|
|
1473
|
+
|
|
1474
|
+
@property
|
|
1475
|
+
def maxmemory(self) -> int:
|
|
1476
|
+
return self._raw.maxmemory()
|
|
1477
|
+
|
|
1478
|
+
@property
|
|
1479
|
+
def ttl(self) -> float:
|
|
1480
|
+
return self._raw.ttl()
|
|
1481
|
+
|
|
1482
|
+
def capacity(self) -> int:
|
|
1483
|
+
"""Returns the number of elements the map can hold without reallocating."""
|
|
1484
|
+
return self._raw.capacity()
|
|
1485
|
+
|
|
1486
|
+
def memory(self) -> int:
|
|
1487
|
+
"""Returns the total estimated memory usage of cached entries in bytes."""
|
|
1488
|
+
return self._raw.memory()
|
|
1489
|
+
|
|
1490
|
+
def __len__(self) -> int:
|
|
1491
|
+
return len(self._raw)
|
|
1492
|
+
|
|
1493
|
+
def __sizeof__(self): # pragma: no cover
|
|
1494
|
+
return self._raw.__sizeof__()
|
|
1495
|
+
|
|
1496
|
+
def __contains__(self, key: KT) -> bool:
|
|
1497
|
+
return key in self._raw
|
|
1498
|
+
|
|
1499
|
+
def __bool__(self) -> bool:
|
|
1500
|
+
return not self.is_empty()
|
|
1501
|
+
|
|
1502
|
+
def is_empty(self) -> bool:
|
|
1503
|
+
return self._raw.is_empty()
|
|
1504
|
+
|
|
1505
|
+
def is_full(self) -> bool:
|
|
1506
|
+
return self._raw.is_full()
|
|
1507
|
+
|
|
1508
|
+
def insert(self, key: KT, value: VT) -> typing.Optional[VT]:
|
|
1509
|
+
"""
|
|
1510
|
+
Inserts a key-value pair into the cache, returning the previous value if the key existed.
|
|
1511
|
+
|
|
1512
|
+
Equivalent to `self[key] = value`, but with additional return value semantics:
|
|
1513
|
+
|
|
1514
|
+
- If the key was not previously in the cache, returns None.
|
|
1515
|
+
- If the key was already present, updates the value and returns the old value.
|
|
1516
|
+
The key itself is not modified.
|
|
1517
|
+
|
|
1518
|
+
Args:
|
|
1519
|
+
key: The key to insert.
|
|
1520
|
+
value: The value to associate with the key.
|
|
1521
|
+
|
|
1522
|
+
Returns:
|
|
1523
|
+
The previous value associated with the key, or None if the key was not present.
|
|
1524
|
+
"""
|
|
1525
|
+
return self._raw.insert(key, value)
|
|
1526
|
+
|
|
1527
|
+
def get(self, key: KT, default: typing.Optional[DT] = None) -> typing.Union[VT, DT]:
|
|
1528
|
+
"""
|
|
1529
|
+
Retrieves the value for a given key from the cache.
|
|
1530
|
+
|
|
1531
|
+
Returns the value associated with the key if present, otherwise returns the specified default value.
|
|
1532
|
+
Equivalent to `self[key]`, but provides a fallback default if the key is not found.
|
|
1533
|
+
|
|
1534
|
+
Args:
|
|
1535
|
+
key: The key to look up in the cache.
|
|
1536
|
+
default: The value to return if the key is not present in the cache. Defaults to None.
|
|
1537
|
+
|
|
1538
|
+
Returns:
|
|
1539
|
+
The value associated with the key, or the default value if the key is not found.
|
|
1540
|
+
"""
|
|
1541
|
+
try:
|
|
1542
|
+
return self._raw.get(key).value()
|
|
1543
|
+
except _core.CoreKeyError:
|
|
1544
|
+
return default # type: ignore[return-value]
|
|
1545
|
+
|
|
1546
|
+
def get_with_expire(
|
|
1547
|
+
self, key: KT, default: typing.Optional[DT] = None
|
|
1548
|
+
) -> typing.Tuple[typing.Union[VT, DT], float]:
|
|
1549
|
+
"""
|
|
1550
|
+
Retrieves the value and expiration duration for a given key from the cache.
|
|
1551
|
+
|
|
1552
|
+
Returns a tuple containing the value associated with the key and its duration.
|
|
1553
|
+
If the key is not found, returns the default value and 0.0 duration.
|
|
1554
|
+
|
|
1555
|
+
Args:
|
|
1556
|
+
key: The key to look up in the cache.
|
|
1557
|
+
default: The value to return if the key is not present in the cache. Defaults to None.
|
|
1558
|
+
|
|
1559
|
+
Returns:
|
|
1560
|
+
A tuple of (value, duration), where value is the cached value or default,
|
|
1561
|
+
and duration is the time-to-live for the key (or 0.0 if not found).
|
|
1562
|
+
"""
|
|
1563
|
+
try:
|
|
1564
|
+
pair = self._raw.get(key)
|
|
1565
|
+
except _core.CoreKeyError:
|
|
1566
|
+
return default, 0.0 # type: ignore[return-value]
|
|
1567
|
+
else:
|
|
1568
|
+
return (pair.value(), pair.duration())
|
|
1569
|
+
|
|
1570
|
+
def pop(self, key: KT, default: typing.Optional[DT] = None) -> typing.Union[VT, DT]:
|
|
1571
|
+
"""
|
|
1572
|
+
Removes specified key and return the corresponding value. If the key is not found, returns the `default`.
|
|
1573
|
+
"""
|
|
1574
|
+
try:
|
|
1575
|
+
return self._raw.remove(key).value()
|
|
1576
|
+
except _core.CoreKeyError:
|
|
1577
|
+
return default # type: ignore[return-value]
|
|
1578
|
+
|
|
1579
|
+
def pop_with_expire(
|
|
1580
|
+
self, key: KT, default: typing.Optional[DT] = None
|
|
1581
|
+
) -> typing.Tuple[typing.Union[VT, DT], float]:
|
|
1582
|
+
"""
|
|
1583
|
+
Removes the specified key from the cache and returns its value and expiration duration.
|
|
1584
|
+
|
|
1585
|
+
If the key is not found, returns the default value and 0.0 duration.
|
|
1586
|
+
|
|
1587
|
+
Args:
|
|
1588
|
+
key: The key to remove from the cache.
|
|
1589
|
+
default: The value to return if the key is not present in the cache. Defaults to None.
|
|
1590
|
+
|
|
1591
|
+
Returns:
|
|
1592
|
+
A tuple of (value, duration), where value is the cached value or default,
|
|
1593
|
+
and duration is the time-to-live for the key (or 0.0 if not found).
|
|
1594
|
+
"""
|
|
1595
|
+
try:
|
|
1596
|
+
pair = self._raw.remove(key)
|
|
1597
|
+
except _core.CoreKeyError:
|
|
1598
|
+
return default, 0.0 # type: ignore[return-value]
|
|
1599
|
+
else:
|
|
1600
|
+
return (pair.value(), pair.duration())
|
|
1601
|
+
|
|
1602
|
+
def setdefault(self, key: KT, default: typing.Optional[DT] = None) -> typing.Union[VT, DT]:
|
|
1603
|
+
"""
|
|
1604
|
+
Inserts key with a value of default if key is not in the cache.
|
|
1605
|
+
|
|
1606
|
+
Return the value for key if key is in the cache, else default.
|
|
1607
|
+
"""
|
|
1608
|
+
return self._raw.setdefault(key, default)
|
|
1609
|
+
|
|
1610
|
+
def popitem(self) -> typing.Tuple[KT, VT]:
|
|
1611
|
+
"""Removes the element that has been in the cache the longest."""
|
|
1612
|
+
try:
|
|
1613
|
+
val = self._raw.popitem()
|
|
1614
|
+
except _core.CoreKeyError:
|
|
1615
|
+
raise KeyError() from None
|
|
1616
|
+
else:
|
|
1617
|
+
return val.pack2()
|
|
1618
|
+
|
|
1619
|
+
def popitem_with_expire(self) -> typing.Tuple[KT, VT, float]:
|
|
1620
|
+
"""
|
|
1621
|
+
Removes and returns the element that has been in the cache the longest, along with its key and expiration duration.
|
|
1622
|
+
|
|
1623
|
+
If the cache is empty, raises a KeyError.
|
|
1624
|
+
|
|
1625
|
+
Returns:
|
|
1626
|
+
A tuple of (key, value, duration), where:
|
|
1627
|
+
- key is the key of the removed item
|
|
1628
|
+
- value is the value of the removed item
|
|
1629
|
+
- duration is the time-to-live for the removed item
|
|
1630
|
+
"""
|
|
1631
|
+
try:
|
|
1632
|
+
val = self._raw.popitem()
|
|
1633
|
+
except _core.CoreKeyError:
|
|
1634
|
+
raise KeyError() from None
|
|
1635
|
+
else:
|
|
1636
|
+
return val.pack3()
|
|
1637
|
+
|
|
1638
|
+
def drain(self, n: int) -> int: # pragma: no cover
|
|
1639
|
+
"""Does the `popitem()` `n` times and returns count of removed items."""
|
|
1640
|
+
if n <= 0:
|
|
1641
|
+
return 0
|
|
1642
|
+
|
|
1643
|
+
for i in range(n):
|
|
1644
|
+
try:
|
|
1645
|
+
self._raw.popitem()
|
|
1646
|
+
except _core.CoreKeyError:
|
|
1647
|
+
return i
|
|
1648
|
+
|
|
1649
|
+
return i
|
|
1650
|
+
|
|
1651
|
+
def update(self, iterable: typing.Union[dict, typing.Iterable[tuple]]) -> None:
|
|
1652
|
+
"""Updates the cache with elements from a dictionary or an iterable object of key/value pairs."""
|
|
1653
|
+
if hasattr(iterable, "items"):
|
|
1654
|
+
iterable = iterable.items()
|
|
1655
|
+
|
|
1656
|
+
self._raw.update(iterable)
|
|
1657
|
+
|
|
1658
|
+
def __setitem__(self, key: KT, value: VT) -> None:
|
|
1659
|
+
self.insert(key, value)
|
|
1660
|
+
|
|
1661
|
+
def __getitem__(self, key: KT) -> VT:
|
|
1662
|
+
try:
|
|
1663
|
+
return self._raw.get(key).value()
|
|
1664
|
+
except _core.CoreKeyError:
|
|
1665
|
+
raise KeyError(key) from None
|
|
1666
|
+
|
|
1667
|
+
def __delitem__(self, key: KT) -> None:
|
|
1668
|
+
try:
|
|
1669
|
+
self._raw.remove(key)
|
|
1670
|
+
except _core.CoreKeyError:
|
|
1671
|
+
raise KeyError(key) from None
|
|
1672
|
+
|
|
1673
|
+
def __eq__(self, other) -> bool:
|
|
1674
|
+
if not isinstance(other, TTLCache):
|
|
1675
|
+
return False # pragma: no cover
|
|
1676
|
+
|
|
1677
|
+
return self._raw == other._raw
|
|
1678
|
+
|
|
1679
|
+
def __ne__(self, other) -> bool:
|
|
1680
|
+
if not isinstance(other, TTLCache):
|
|
1681
|
+
return False # pragma: no cover
|
|
1682
|
+
|
|
1683
|
+
return self._raw != other._raw
|
|
1684
|
+
|
|
1685
|
+
def shrink_to_fit(self) -> None:
|
|
1686
|
+
"""Shrinks the cache to fit len(self) elements."""
|
|
1687
|
+
self._raw.shrink_to_fit()
|
|
1688
|
+
|
|
1689
|
+
def clear(self, *, reuse: bool = False) -> None:
|
|
1690
|
+
"""
|
|
1691
|
+
Removes all items from cache.
|
|
1692
|
+
|
|
1693
|
+
If reuse is True, will not free the memory for reusing in the future.
|
|
1694
|
+
"""
|
|
1695
|
+
self._raw.clear(reuse)
|
|
1696
|
+
|
|
1697
|
+
def items_with_expire(self) -> IteratorView[typing.Tuple[KT, VT, float]]:
|
|
1698
|
+
"""
|
|
1699
|
+
Returns an iterable object of the cache's items (key-value pairs along with their expiration duration).
|
|
1700
|
+
|
|
1701
|
+
Notes:
|
|
1702
|
+
- You should not make any changes in cache while using this iterable object.
|
|
1703
|
+
"""
|
|
1704
|
+
return IteratorView(self._raw.items(), lambda x: x.pack3())
|
|
1705
|
+
|
|
1706
|
+
def items(self) -> IteratorView[typing.Tuple[KT, VT]]:
|
|
1707
|
+
"""
|
|
1708
|
+
Returns an iterable object of the cache's items (key-value pairs).
|
|
1709
|
+
|
|
1710
|
+
Notes:
|
|
1711
|
+
- You should not make any changes in cache while using this iterable object.
|
|
1712
|
+
"""
|
|
1713
|
+
return IteratorView(self._raw.items(), lambda x: x.pack2())
|
|
1714
|
+
|
|
1715
|
+
def keys(self) -> IteratorView[KT]:
|
|
1716
|
+
"""
|
|
1717
|
+
Returns an iterable object of the cache's keys.
|
|
1718
|
+
|
|
1719
|
+
Notes:
|
|
1720
|
+
- You should not make any changes in cache while using this iterable object.
|
|
1721
|
+
"""
|
|
1722
|
+
return IteratorView(self._raw.items(), lambda x: x.key())
|
|
1723
|
+
|
|
1724
|
+
def values(self) -> IteratorView[VT]:
|
|
1725
|
+
"""
|
|
1726
|
+
Returns an iterable object of the cache's values.
|
|
1727
|
+
|
|
1728
|
+
Notes:
|
|
1729
|
+
- You should not make any changes in cache while using this iterable object.
|
|
1730
|
+
"""
|
|
1731
|
+
return IteratorView(self._raw.items(), lambda x: x.value())
|
|
1732
|
+
|
|
1733
|
+
def first(self, n: int = 0) -> typing.Optional[KT]: # pragma: no cover
|
|
1734
|
+
"""
|
|
1735
|
+
Returns the first key in cache; this is the one which will be removed by `popitem()` (if n == 0).
|
|
1736
|
+
|
|
1737
|
+
By using `n` parameter, you can browse order index by index.
|
|
1738
|
+
"""
|
|
1739
|
+
if n < 0:
|
|
1740
|
+
n = len(self._raw) + n
|
|
1741
|
+
|
|
1742
|
+
if n < 0:
|
|
1743
|
+
return None
|
|
1744
|
+
|
|
1745
|
+
return self._raw.get_index(n)
|
|
1746
|
+
|
|
1747
|
+
def last(self) -> typing.Optional[KT]:
|
|
1748
|
+
"""
|
|
1749
|
+
Returns the last key in cache. Equals to `self.first(-1)`.
|
|
1750
|
+
"""
|
|
1751
|
+
return self._raw.get_index(len(self._raw) - 1)
|
|
1752
|
+
|
|
1753
|
+
def expire(self) -> None: # pragma: no cover
|
|
1754
|
+
"""
|
|
1755
|
+
Manually removes expired key-value pairs from memory and releases their memory.
|
|
1756
|
+
|
|
1757
|
+
Notes:
|
|
1758
|
+
- This operation is typically automatic and does not require manual invocation.
|
|
1759
|
+
"""
|
|
1760
|
+
self._raw.expire()
|
|
1761
|
+
|
|
1762
|
+
def copy(self) -> "TTLCache[KT, VT]":
|
|
1763
|
+
"""Returns a shallow copy of the cache"""
|
|
1764
|
+
return self.__copy__()
|
|
1765
|
+
|
|
1766
|
+
def __copy__(self) -> "TTLCache[KT, VT]":
|
|
1767
|
+
cls = type(self)
|
|
1768
|
+
copied = cls.__new__(cls)
|
|
1769
|
+
copied._raw = _std_copy.copy(self._raw)
|
|
1770
|
+
return copied
|
|
1771
|
+
|
|
1772
|
+
def __deepcopy__(self, memo) -> "TTLCache[KT, VT]":
|
|
1773
|
+
cls = type(self)
|
|
1774
|
+
copied = cls.__new__(cls)
|
|
1775
|
+
copied._raw = _std_copy.deepcopy(self._raw, memo)
|
|
1776
|
+
return copied
|
|
1777
|
+
|
|
1778
|
+
def __iter__(self) -> IteratorView[KT]:
|
|
1779
|
+
return self.keys()
|
|
1780
|
+
|
|
1781
|
+
def __repr__(self) -> str:
|
|
1782
|
+
cls = type(self)
|
|
1783
|
+
|
|
1784
|
+
return "%s.%s[%d/%d, ttl=%f](%s)" % (
|
|
1785
|
+
cls.__module__,
|
|
1786
|
+
cls.__name__,
|
|
1787
|
+
len(self._raw),
|
|
1788
|
+
self._raw.maxsize(),
|
|
1789
|
+
self._raw.ttl(),
|
|
1790
|
+
_items_to_str(self.items(), len(self._raw)),
|
|
1791
|
+
)
|
|
1792
|
+
|
|
1793
|
+
|
|
1794
|
+
class VTTLCache(BaseCacheImpl[KT, VT]):
|
|
1795
|
+
"""
|
|
1796
|
+
A thread-safe, time-to-live (TTL) cache implementation with per-key expiration policy.
|
|
1797
|
+
|
|
1798
|
+
This cache allows storing key-value pairs with optional expiration times. When an item expires,
|
|
1799
|
+
it is automatically removed from the cache. The cache supports a maximum size and provides
|
|
1800
|
+
various methods for inserting, retrieving, and managing cached items.
|
|
1801
|
+
|
|
1802
|
+
Key features:
|
|
1803
|
+
- Per-key time-to-live (TTL) support
|
|
1804
|
+
- Configurable maximum cache size
|
|
1805
|
+
- Thread-safe operations
|
|
1806
|
+
- Automatic expiration of items
|
|
1807
|
+
|
|
1808
|
+
Supports dictionary-like operations such as get, insert, update, and iteration.
|
|
1809
|
+
"""
|
|
1810
|
+
|
|
1811
|
+
__slots__ = ("_raw",)
|
|
1812
|
+
|
|
1813
|
+
def __init__(
|
|
1814
|
+
self,
|
|
1815
|
+
maxsize: int,
|
|
1816
|
+
iterable: typing.Union[typing.Union[dict, typing.Iterable[tuple]], None] = None,
|
|
1817
|
+
ttl: typing.Union[float, timedelta, datetime, None] = None, # This is not a global TTL!
|
|
1818
|
+
*,
|
|
1819
|
+
capacity: int = 0,
|
|
1820
|
+
maxmemory: int = 0,
|
|
1821
|
+
) -> None:
|
|
1822
|
+
"""
|
|
1823
|
+
Initialize a new VTTLCache instance.
|
|
1824
|
+
|
|
1825
|
+
Args:
|
|
1826
|
+
maxsize (int): Maximum size of the cache. Zero indicates unlimited size.
|
|
1827
|
+
iterable (dict or Iterable[tuple], optional): Initial data to populate the cache.
|
|
1828
|
+
ttl (float or timedelta or datetime, optional): Time-to-live duration for `iterable` items.
|
|
1829
|
+
capacity (int, optional): Preallocated capacity for the cache to minimize reallocations.
|
|
1830
|
+
maxmemory (int, optional): Maximum memory (bytes) allowed for cached entries. Zero means unlimited.
|
|
1831
|
+
On PyPy. In PyPy, the size of each object is assumed to be 1 if the object
|
|
1832
|
+
does not have a `__sizeof__` method.
|
|
1833
|
+
|
|
1834
|
+
Raises:
|
|
1835
|
+
ValueError: If provided TTL is zero or negative.
|
|
1836
|
+
"""
|
|
1837
|
+
self._raw = _core.VTTLCache(maxsize, capacity=capacity, maxmemory=maxmemory)
|
|
1838
|
+
|
|
1839
|
+
if iterable is not None:
|
|
1840
|
+
self.update(iterable, ttl)
|
|
1841
|
+
|
|
1842
|
+
@property
|
|
1843
|
+
def maxsize(self) -> int:
|
|
1844
|
+
return self._raw.maxsize()
|
|
1845
|
+
|
|
1846
|
+
@property
|
|
1847
|
+
def maxmemory(self) -> int:
|
|
1848
|
+
return self._raw.maxmemory()
|
|
1849
|
+
|
|
1850
|
+
def capacity(self) -> int:
|
|
1851
|
+
"""Returns the number of elements the map can hold without reallocating."""
|
|
1852
|
+
return self._raw.capacity()
|
|
1853
|
+
|
|
1854
|
+
def memory(self) -> int:
|
|
1855
|
+
"""Returns the total estimated memory usage of cached entries in bytes."""
|
|
1856
|
+
return self._raw.memory()
|
|
1857
|
+
|
|
1858
|
+
def __len__(self) -> int:
|
|
1859
|
+
return len(self._raw)
|
|
1860
|
+
|
|
1861
|
+
def __sizeof__(self): # pragma: no cover
|
|
1862
|
+
return self._raw.__sizeof__()
|
|
1863
|
+
|
|
1864
|
+
def __contains__(self, key: KT) -> bool:
|
|
1865
|
+
return key in self._raw
|
|
1866
|
+
|
|
1867
|
+
def __bool__(self) -> bool:
|
|
1868
|
+
return not self.is_empty()
|
|
1869
|
+
|
|
1870
|
+
def is_empty(self) -> bool:
|
|
1871
|
+
return self._raw.is_empty()
|
|
1872
|
+
|
|
1873
|
+
def is_full(self) -> bool:
|
|
1874
|
+
return self._raw.is_full()
|
|
1875
|
+
|
|
1876
|
+
def insert(
|
|
1877
|
+
self,
|
|
1878
|
+
key: KT,
|
|
1879
|
+
value: VT,
|
|
1880
|
+
ttl: typing.Union[float, timedelta, datetime, None] = None,
|
|
1881
|
+
) -> typing.Optional[VT]:
|
|
1882
|
+
"""
|
|
1883
|
+
Insert a key-value pair into the cache with an optional time-to-live (TTL).
|
|
1884
|
+
Returns the previous value associated with the key, if it existed.
|
|
1885
|
+
|
|
1886
|
+
Args:
|
|
1887
|
+
key (KT): The key to insert.
|
|
1888
|
+
value (VT): The value to associate with the key.
|
|
1889
|
+
ttl (float or timedelta or datetime, optional): Time-to-live duration for the item.
|
|
1890
|
+
If a timedelta or datetime is provided, it will be converted to seconds.
|
|
1891
|
+
|
|
1892
|
+
Raises:
|
|
1893
|
+
ValueError: If the provided TTL is zero or negative.
|
|
1894
|
+
"""
|
|
1895
|
+
if ttl is not None: # pragma: no cover
|
|
1896
|
+
if isinstance(ttl, timedelta):
|
|
1897
|
+
ttl = ttl.total_seconds()
|
|
1898
|
+
|
|
1899
|
+
elif isinstance(ttl, datetime):
|
|
1900
|
+
ttl = (ttl - datetime.now()).total_seconds()
|
|
1901
|
+
|
|
1902
|
+
if ttl <= 0:
|
|
1903
|
+
raise ValueError("ttl must be positive and non-zero")
|
|
1904
|
+
|
|
1905
|
+
return self._raw.insert(key, value, ttl)
|
|
1906
|
+
|
|
1907
|
+
def get(self, key: KT, default: typing.Optional[DT] = None) -> typing.Union[VT, DT]:
|
|
1908
|
+
"""
|
|
1909
|
+
Retrieves the value for a given key from the cache.
|
|
1910
|
+
|
|
1911
|
+
Returns the value associated with the key if present, otherwise returns the specified default value.
|
|
1912
|
+
Equivalent to `self[key]`, but provides a fallback default if the key is not found.
|
|
1913
|
+
|
|
1914
|
+
Args:
|
|
1915
|
+
key: The key to look up in the cache.
|
|
1916
|
+
default: The value to return if the key is not present in the cache. Defaults to None.
|
|
1917
|
+
|
|
1918
|
+
Returns:
|
|
1919
|
+
The value associated with the key, or the default value if the key is not found.
|
|
1920
|
+
"""
|
|
1921
|
+
try:
|
|
1922
|
+
return self._raw.get(key).value()
|
|
1923
|
+
except _core.CoreKeyError:
|
|
1924
|
+
return default # type: ignore[return-value]
|
|
1925
|
+
|
|
1926
|
+
def get_with_expire(
|
|
1927
|
+
self, key: KT, default: typing.Optional[DT] = None
|
|
1928
|
+
) -> typing.Tuple[typing.Union[VT, DT], float]:
|
|
1929
|
+
"""
|
|
1930
|
+
Retrieves the value and expiration duration for a given key from the cache.
|
|
1931
|
+
|
|
1932
|
+
Returns a tuple containing the value associated with the key and its duration.
|
|
1933
|
+
If the key is not found, returns the default value and 0.0 duration.
|
|
1934
|
+
|
|
1935
|
+
Args:
|
|
1936
|
+
key: The key to look up in the cache.
|
|
1937
|
+
default: The value to return if the key is not present in the cache. Defaults to None.
|
|
1938
|
+
|
|
1939
|
+
Returns:
|
|
1940
|
+
A tuple of (value, duration), where value is the cached value or default,
|
|
1941
|
+
and duration is the time-to-live for the key (or 0.0 if not found).
|
|
1942
|
+
"""
|
|
1943
|
+
try:
|
|
1944
|
+
pair = self._raw.get(key)
|
|
1945
|
+
except _core.CoreKeyError:
|
|
1946
|
+
return default, 0.0 # type: ignore[return-value]
|
|
1947
|
+
else:
|
|
1948
|
+
return (pair.value(), pair.duration())
|
|
1949
|
+
|
|
1950
|
+
def pop(self, key: KT, default: typing.Optional[DT] = None) -> typing.Union[VT, DT]:
|
|
1951
|
+
"""
|
|
1952
|
+
Removes specified key and return the corresponding value. If the key is not found, returns the `default`.
|
|
1953
|
+
"""
|
|
1954
|
+
try:
|
|
1955
|
+
return self._raw.remove(key).value()
|
|
1956
|
+
except _core.CoreKeyError:
|
|
1957
|
+
return default # type: ignore[return-value]
|
|
1958
|
+
|
|
1959
|
+
def pop_with_expire(
|
|
1960
|
+
self, key: KT, default: typing.Optional[DT] = None
|
|
1961
|
+
) -> typing.Tuple[typing.Union[VT, DT], float]:
|
|
1962
|
+
"""
|
|
1963
|
+
Removes the specified key from the cache and returns its value and expiration duration.
|
|
1964
|
+
|
|
1965
|
+
If the key is not found, returns the default value and 0.0 duration.
|
|
1966
|
+
|
|
1967
|
+
Args:
|
|
1968
|
+
key: The key to remove from the cache.
|
|
1969
|
+
default: The value to return if the key is not present in the cache. Defaults to None.
|
|
1970
|
+
|
|
1971
|
+
Returns:
|
|
1972
|
+
A tuple of (value, duration), where value is the cached value or default,
|
|
1973
|
+
and duration is the time-to-live for the key (or 0.0 if not found).
|
|
1974
|
+
"""
|
|
1975
|
+
try:
|
|
1976
|
+
pair = self._raw.remove(key)
|
|
1977
|
+
except _core.CoreKeyError:
|
|
1978
|
+
return default, 0.0 # type: ignore[return-value]
|
|
1979
|
+
else:
|
|
1980
|
+
return (pair.value(), pair.duration())
|
|
1981
|
+
|
|
1982
|
+
def setdefault(
|
|
1983
|
+
self,
|
|
1984
|
+
key: KT,
|
|
1985
|
+
default: typing.Optional[DT] = None,
|
|
1986
|
+
ttl: typing.Union[float, timedelta, datetime, None] = None,
|
|
1987
|
+
) -> typing.Union[VT, DT]:
|
|
1988
|
+
"""
|
|
1989
|
+
Inserts a key-value pair into the cache with an optional time-to-live (TTL).
|
|
1990
|
+
|
|
1991
|
+
If the key is not in the cache, it will be inserted with the default value.
|
|
1992
|
+
If the key already exists, its current value is returned.
|
|
1993
|
+
|
|
1994
|
+
Args:
|
|
1995
|
+
key: The key to insert or retrieve from the cache.
|
|
1996
|
+
default: The value to insert if the key is not present. Defaults to None.
|
|
1997
|
+
ttl: Optional time-to-live for the key. Can be a float (seconds), timedelta, or datetime.
|
|
1998
|
+
If not specified, the key will not expire.
|
|
1999
|
+
|
|
2000
|
+
Returns:
|
|
2001
|
+
The value associated with the key, either existing or the default value.
|
|
2002
|
+
|
|
2003
|
+
Raises:
|
|
2004
|
+
ValueError: If the provided TTL is not a positive value.
|
|
2005
|
+
"""
|
|
2006
|
+
if ttl is not None: # pragma: no cover
|
|
2007
|
+
if isinstance(ttl, timedelta):
|
|
2008
|
+
ttl = ttl.total_seconds()
|
|
2009
|
+
|
|
2010
|
+
elif isinstance(ttl, datetime):
|
|
2011
|
+
ttl = (ttl - datetime.now()).total_seconds()
|
|
2012
|
+
|
|
2013
|
+
if ttl <= 0:
|
|
2014
|
+
raise ValueError("ttl must be positive and non-zero")
|
|
2015
|
+
|
|
2016
|
+
return self._raw.setdefault(key, default, ttl)
|
|
2017
|
+
|
|
2018
|
+
def popitem(self) -> typing.Tuple[KT, VT]:
|
|
2019
|
+
"""
|
|
2020
|
+
Removes and returns the key-value pair that is closest to expiration.
|
|
2021
|
+
|
|
2022
|
+
Returns:
|
|
2023
|
+
A tuple containing the key and value of the removed item.
|
|
2024
|
+
|
|
2025
|
+
Raises:
|
|
2026
|
+
KeyError: If the cache is empty.
|
|
2027
|
+
"""
|
|
2028
|
+
try:
|
|
2029
|
+
val = self._raw.popitem()
|
|
2030
|
+
except _core.CoreKeyError: # pragma: no cover
|
|
2031
|
+
raise KeyError() from None
|
|
2032
|
+
else:
|
|
2033
|
+
return val.pack2()
|
|
2034
|
+
|
|
2035
|
+
def popitem_with_expire(self) -> typing.Tuple[KT, VT, float]:
|
|
2036
|
+
"""
|
|
2037
|
+
Removes and returns the key-value pair that is closest to expiration, along with its expiration duration.
|
|
2038
|
+
|
|
2039
|
+
Returns:
|
|
2040
|
+
A tuple containing the key, value, and expiration duration of the removed item.
|
|
2041
|
+
|
|
2042
|
+
Raises:
|
|
2043
|
+
KeyError: If the cache is empty.
|
|
2044
|
+
"""
|
|
2045
|
+
try:
|
|
2046
|
+
val = self._raw.popitem()
|
|
2047
|
+
except _core.CoreKeyError:
|
|
2048
|
+
raise KeyError() from None
|
|
2049
|
+
else:
|
|
2050
|
+
return val.pack3()
|
|
2051
|
+
|
|
2052
|
+
def drain(self, n: int) -> int: # pragma: no cover
|
|
2053
|
+
"""Does the `popitem()` `n` times and returns count of removed items."""
|
|
2054
|
+
if n <= 0:
|
|
2055
|
+
return 0
|
|
2056
|
+
|
|
2057
|
+
for i in range(n):
|
|
2058
|
+
try:
|
|
2059
|
+
self._raw.popitem()
|
|
2060
|
+
except _core.CoreKeyError:
|
|
2061
|
+
return i
|
|
2062
|
+
|
|
2063
|
+
return i
|
|
2064
|
+
|
|
2065
|
+
def update(
|
|
2066
|
+
self,
|
|
2067
|
+
iterable: typing.Union[dict, typing.Iterable[tuple]],
|
|
2068
|
+
ttl: typing.Union[float, timedelta, datetime, None] = None,
|
|
2069
|
+
) -> None:
|
|
2070
|
+
"""Updates the cache with elements from a dictionary or an iterable object of key/value pairs."""
|
|
2071
|
+
if hasattr(iterable, "items"):
|
|
2072
|
+
iterable = iterable.items()
|
|
2073
|
+
|
|
2074
|
+
if ttl is not None: # pragma: no cover
|
|
2075
|
+
if isinstance(ttl, timedelta):
|
|
2076
|
+
ttl = ttl.total_seconds()
|
|
2077
|
+
|
|
2078
|
+
elif isinstance(ttl, datetime):
|
|
2079
|
+
ttl = (ttl - datetime.now()).total_seconds()
|
|
2080
|
+
|
|
2081
|
+
if ttl <= 0:
|
|
2082
|
+
raise ValueError("ttl must be positive and non-zero")
|
|
2083
|
+
|
|
2084
|
+
self._raw.update(iterable, ttl)
|
|
2085
|
+
|
|
2086
|
+
def __setitem__(self, key: KT, value: VT) -> None:
|
|
2087
|
+
self.insert(key, value, None)
|
|
2088
|
+
|
|
2089
|
+
def __getitem__(self, key: KT) -> VT:
|
|
2090
|
+
try:
|
|
2091
|
+
return self._raw.get(key).value()
|
|
2092
|
+
except _core.CoreKeyError:
|
|
2093
|
+
raise KeyError(key) from None
|
|
2094
|
+
|
|
2095
|
+
def __delitem__(self, key: KT) -> None:
|
|
2096
|
+
try:
|
|
2097
|
+
self._raw.remove(key)
|
|
2098
|
+
except _core.CoreKeyError:
|
|
2099
|
+
raise KeyError(key) from None
|
|
2100
|
+
|
|
2101
|
+
def __eq__(self, other) -> bool:
|
|
2102
|
+
if not isinstance(other, VTTLCache):
|
|
2103
|
+
return False # pragma: no cover
|
|
2104
|
+
|
|
2105
|
+
return self._raw == other._raw
|
|
2106
|
+
|
|
2107
|
+
def __ne__(self, other) -> bool:
|
|
2108
|
+
if not isinstance(other, VTTLCache):
|
|
2109
|
+
return False # pragma: no cover
|
|
2110
|
+
|
|
2111
|
+
return self._raw != other._raw
|
|
2112
|
+
|
|
2113
|
+
def shrink_to_fit(self) -> None:
|
|
2114
|
+
"""Shrinks the cache to fit len(self) elements."""
|
|
2115
|
+
self._raw.shrink_to_fit()
|
|
2116
|
+
|
|
2117
|
+
def clear(self, *, reuse: bool = False) -> None:
|
|
2118
|
+
"""
|
|
2119
|
+
Removes all items from cache.
|
|
2120
|
+
|
|
2121
|
+
If reuse is True, will not free the memory for reusing in the future.
|
|
2122
|
+
"""
|
|
2123
|
+
self._raw.clear(reuse)
|
|
2124
|
+
|
|
2125
|
+
def items_with_expire(self) -> IteratorView[typing.Tuple[KT, VT, float]]:
|
|
2126
|
+
"""
|
|
2127
|
+
Returns an iterable object of the cache's items (key-value pairs along with their expiration duration).
|
|
2128
|
+
|
|
2129
|
+
Notes:
|
|
2130
|
+
- You should not make any changes in cache while using this iterable object.
|
|
2131
|
+
"""
|
|
2132
|
+
return IteratorView(self._raw.items(), lambda x: x.pack3())
|
|
2133
|
+
|
|
2134
|
+
def items(self) -> IteratorView[typing.Tuple[KT, VT]]:
|
|
2135
|
+
"""
|
|
2136
|
+
Returns an iterable object of the cache's items (key-value pairs).
|
|
2137
|
+
|
|
2138
|
+
Notes:
|
|
2139
|
+
- You should not make any changes in cache while using this iterable object.
|
|
2140
|
+
"""
|
|
2141
|
+
return IteratorView(self._raw.items(), lambda x: x.pack2())
|
|
2142
|
+
|
|
2143
|
+
def keys(self) -> IteratorView[KT]:
|
|
2144
|
+
"""
|
|
2145
|
+
Returns an iterable object of the cache's keys.
|
|
2146
|
+
|
|
2147
|
+
Notes:
|
|
2148
|
+
- You should not make any changes in cache while using this iterable object.
|
|
2149
|
+
"""
|
|
2150
|
+
return IteratorView(self._raw.items(), lambda x: x.key())
|
|
2151
|
+
|
|
2152
|
+
def values(self) -> IteratorView[VT]:
|
|
2153
|
+
"""
|
|
2154
|
+
Returns an iterable object of the cache's values.
|
|
2155
|
+
|
|
2156
|
+
Notes:
|
|
2157
|
+
- You should not make any changes in cache while using this iterable object.
|
|
2158
|
+
"""
|
|
2159
|
+
return IteratorView(self._raw.items(), lambda x: x.value())
|
|
2160
|
+
|
|
2161
|
+
def expire(self) -> None: # pragma: no cover
|
|
2162
|
+
"""
|
|
2163
|
+
Manually removes expired key-value pairs from memory and releases their memory.
|
|
2164
|
+
|
|
2165
|
+
Notes:
|
|
2166
|
+
- This operation is typically automatic and does not require manual invocation.
|
|
2167
|
+
"""
|
|
2168
|
+
self._raw.expire()
|
|
2169
|
+
|
|
2170
|
+
def copy(self) -> "VTTLCache[KT, VT]":
|
|
2171
|
+
"""Returns a shallow copy of the cache"""
|
|
2172
|
+
return self.__copy__()
|
|
2173
|
+
|
|
2174
|
+
def __copy__(self) -> "VTTLCache[KT, VT]":
|
|
2175
|
+
cls = type(self)
|
|
2176
|
+
copied = cls.__new__(cls)
|
|
2177
|
+
copied._raw = _std_copy.copy(self._raw)
|
|
2178
|
+
return copied
|
|
2179
|
+
|
|
2180
|
+
def __deepcopy__(self, memo) -> "VTTLCache[KT, VT]":
|
|
2181
|
+
cls = type(self)
|
|
2182
|
+
copied = cls.__new__(cls)
|
|
2183
|
+
copied._raw = _std_copy.deepcopy(self._raw, memo)
|
|
2184
|
+
return copied
|
|
2185
|
+
|
|
2186
|
+
def __iter__(self) -> IteratorView[KT]:
|
|
2187
|
+
return self.keys()
|
|
2188
|
+
|
|
2189
|
+
def __repr__(self) -> str:
|
|
2190
|
+
cls = type(self)
|
|
2191
|
+
|
|
2192
|
+
return "%s.%s[%d/%d](%s)" % (
|
|
2193
|
+
cls.__module__,
|
|
2194
|
+
cls.__name__,
|
|
2195
|
+
len(self._raw),
|
|
2196
|
+
self._raw.maxsize(),
|
|
2197
|
+
_items_to_str(self.items(), len(self._raw)),
|
|
2198
|
+
)
|