Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/cachetools/__init__.py: 21%
582 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:40 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:40 +0000
1"""Extensible memoizing collections and decorators."""
3__all__ = (
4 "Cache",
5 "FIFOCache",
6 "LFUCache",
7 "LRUCache",
8 "MRUCache",
9 "RRCache",
10 "TLRUCache",
11 "TTLCache",
12 "cached",
13 "cachedmethod",
14)
16__version__ = "5.3.2"
18import collections
19import collections.abc
20import functools
21import heapq
22import random
23import time
25from . import keys
28class _DefaultSize:
30 __slots__ = ()
32 def __getitem__(self, _):
33 return 1
35 def __setitem__(self, _, value):
36 assert value == 1
38 def pop(self, _):
39 return 1
42class Cache(collections.abc.MutableMapping):
43 """Mutable mapping to serve as a simple cache or cache base class."""
45 __marker = object()
47 __size = _DefaultSize()
49 def __init__(self, maxsize, getsizeof=None):
50 if getsizeof:
51 self.getsizeof = getsizeof
52 if self.getsizeof is not Cache.getsizeof:
53 self.__size = dict()
54 self.__data = dict()
55 self.__currsize = 0
56 self.__maxsize = maxsize
58 def __repr__(self):
59 return "%s(%s, maxsize=%r, currsize=%r)" % (
60 self.__class__.__name__,
61 repr(self.__data),
62 self.__maxsize,
63 self.__currsize,
64 )
66 def __getitem__(self, key):
67 try:
68 return self.__data[key]
69 except KeyError:
70 return self.__missing__(key)
72 def __setitem__(self, key, value):
73 maxsize = self.__maxsize
74 size = self.getsizeof(value)
75 if size > maxsize:
76 raise ValueError("value too large")
77 if key not in self.__data or self.__size[key] < size:
78 while self.__currsize + size > maxsize:
79 self.popitem()
80 if key in self.__data:
81 diffsize = size - self.__size[key]
82 else:
83 diffsize = size
84 self.__data[key] = value
85 self.__size[key] = size
86 self.__currsize += diffsize
88 def __delitem__(self, key):
89 size = self.__size.pop(key)
90 del self.__data[key]
91 self.__currsize -= size
93 def __contains__(self, key):
94 return key in self.__data
96 def __missing__(self, key):
97 raise KeyError(key)
99 def __iter__(self):
100 return iter(self.__data)
102 def __len__(self):
103 return len(self.__data)
105 def get(self, key, default=None):
106 if key in self:
107 return self[key]
108 else:
109 return default
111 def pop(self, key, default=__marker):
112 if key in self:
113 value = self[key]
114 del self[key]
115 elif default is self.__marker:
116 raise KeyError(key)
117 else:
118 value = default
119 return value
121 def setdefault(self, key, default=None):
122 if key in self:
123 value = self[key]
124 else:
125 self[key] = value = default
126 return value
128 @property
129 def maxsize(self):
130 """The maximum size of the cache."""
131 return self.__maxsize
133 @property
134 def currsize(self):
135 """The current size of the cache."""
136 return self.__currsize
138 @staticmethod
139 def getsizeof(value):
140 """Return the size of a cache element's value."""
141 return 1
144class FIFOCache(Cache):
145 """First In First Out (FIFO) cache implementation."""
147 def __init__(self, maxsize, getsizeof=None):
148 Cache.__init__(self, maxsize, getsizeof)
149 self.__order = collections.OrderedDict()
151 def __setitem__(self, key, value, cache_setitem=Cache.__setitem__):
152 cache_setitem(self, key, value)
153 try:
154 self.__order.move_to_end(key)
155 except KeyError:
156 self.__order[key] = None
158 def __delitem__(self, key, cache_delitem=Cache.__delitem__):
159 cache_delitem(self, key)
160 del self.__order[key]
162 def popitem(self):
163 """Remove and return the `(key, value)` pair first inserted."""
164 try:
165 key = next(iter(self.__order))
166 except StopIteration:
167 raise KeyError("%s is empty" % type(self).__name__) from None
168 else:
169 return (key, self.pop(key))
172class LFUCache(Cache):
173 """Least Frequently Used (LFU) cache implementation."""
175 def __init__(self, maxsize, getsizeof=None):
176 Cache.__init__(self, maxsize, getsizeof)
177 self.__counter = collections.Counter()
179 def __getitem__(self, key, cache_getitem=Cache.__getitem__):
180 value = cache_getitem(self, key)
181 if key in self: # __missing__ may not store item
182 self.__counter[key] -= 1
183 return value
185 def __setitem__(self, key, value, cache_setitem=Cache.__setitem__):
186 cache_setitem(self, key, value)
187 self.__counter[key] -= 1
189 def __delitem__(self, key, cache_delitem=Cache.__delitem__):
190 cache_delitem(self, key)
191 del self.__counter[key]
193 def popitem(self):
194 """Remove and return the `(key, value)` pair least frequently used."""
195 try:
196 ((key, _),) = self.__counter.most_common(1)
197 except ValueError:
198 raise KeyError("%s is empty" % type(self).__name__) from None
199 else:
200 return (key, self.pop(key))
203class LRUCache(Cache):
204 """Least Recently Used (LRU) cache implementation."""
206 def __init__(self, maxsize, getsizeof=None):
207 Cache.__init__(self, maxsize, getsizeof)
208 self.__order = collections.OrderedDict()
210 def __getitem__(self, key, cache_getitem=Cache.__getitem__):
211 value = cache_getitem(self, key)
212 if key in self: # __missing__ may not store item
213 self.__update(key)
214 return value
216 def __setitem__(self, key, value, cache_setitem=Cache.__setitem__):
217 cache_setitem(self, key, value)
218 self.__update(key)
220 def __delitem__(self, key, cache_delitem=Cache.__delitem__):
221 cache_delitem(self, key)
222 del self.__order[key]
224 def popitem(self):
225 """Remove and return the `(key, value)` pair least recently used."""
226 try:
227 key = next(iter(self.__order))
228 except StopIteration:
229 raise KeyError("%s is empty" % type(self).__name__) from None
230 else:
231 return (key, self.pop(key))
233 def __update(self, key):
234 try:
235 self.__order.move_to_end(key)
236 except KeyError:
237 self.__order[key] = None
240class MRUCache(Cache):
241 """Most Recently Used (MRU) cache implementation."""
243 def __init__(self, maxsize, getsizeof=None):
244 Cache.__init__(self, maxsize, getsizeof)
245 self.__order = collections.OrderedDict()
247 def __getitem__(self, key, cache_getitem=Cache.__getitem__):
248 value = cache_getitem(self, key)
249 if key in self: # __missing__ may not store item
250 self.__update(key)
251 return value
253 def __setitem__(self, key, value, cache_setitem=Cache.__setitem__):
254 cache_setitem(self, key, value)
255 self.__update(key)
257 def __delitem__(self, key, cache_delitem=Cache.__delitem__):
258 cache_delitem(self, key)
259 del self.__order[key]
261 def popitem(self):
262 """Remove and return the `(key, value)` pair most recently used."""
263 try:
264 key = next(iter(self.__order))
265 except StopIteration:
266 raise KeyError("%s is empty" % type(self).__name__) from None
267 else:
268 return (key, self.pop(key))
270 def __update(self, key):
271 try:
272 self.__order.move_to_end(key, last=False)
273 except KeyError:
274 self.__order[key] = None
277class RRCache(Cache):
278 """Random Replacement (RR) cache implementation."""
280 def __init__(self, maxsize, choice=random.choice, getsizeof=None):
281 Cache.__init__(self, maxsize, getsizeof)
282 self.__choice = choice
284 @property
285 def choice(self):
286 """The `choice` function used by the cache."""
287 return self.__choice
289 def popitem(self):
290 """Remove and return a random `(key, value)` pair."""
291 try:
292 key = self.__choice(list(self))
293 except IndexError:
294 raise KeyError("%s is empty" % type(self).__name__) from None
295 else:
296 return (key, self.pop(key))
299class _TimedCache(Cache):
300 """Base class for time aware cache implementations."""
302 class _Timer:
303 def __init__(self, timer):
304 self.__timer = timer
305 self.__nesting = 0
307 def __call__(self):
308 if self.__nesting == 0:
309 return self.__timer()
310 else:
311 return self.__time
313 def __enter__(self):
314 if self.__nesting == 0:
315 self.__time = time = self.__timer()
316 else:
317 time = self.__time
318 self.__nesting += 1
319 return time
321 def __exit__(self, *exc):
322 self.__nesting -= 1
324 def __reduce__(self):
325 return _TimedCache._Timer, (self.__timer,)
327 def __getattr__(self, name):
328 return getattr(self.__timer, name)
330 def __init__(self, maxsize, timer=time.monotonic, getsizeof=None):
331 Cache.__init__(self, maxsize, getsizeof)
332 self.__timer = _TimedCache._Timer(timer)
334 def __repr__(self, cache_repr=Cache.__repr__):
335 with self.__timer as time:
336 self.expire(time)
337 return cache_repr(self)
339 def __len__(self, cache_len=Cache.__len__):
340 with self.__timer as time:
341 self.expire(time)
342 return cache_len(self)
344 @property
345 def currsize(self):
346 with self.__timer as time:
347 self.expire(time)
348 return super().currsize
350 @property
351 def timer(self):
352 """The timer function used by the cache."""
353 return self.__timer
355 def clear(self):
356 with self.__timer as time:
357 self.expire(time)
358 Cache.clear(self)
360 def get(self, *args, **kwargs):
361 with self.__timer:
362 return Cache.get(self, *args, **kwargs)
364 def pop(self, *args, **kwargs):
365 with self.__timer:
366 return Cache.pop(self, *args, **kwargs)
368 def setdefault(self, *args, **kwargs):
369 with self.__timer:
370 return Cache.setdefault(self, *args, **kwargs)
373class TTLCache(_TimedCache):
374 """LRU Cache implementation with per-item time-to-live (TTL) value."""
376 class _Link:
378 __slots__ = ("key", "expires", "next", "prev")
380 def __init__(self, key=None, expires=None):
381 self.key = key
382 self.expires = expires
384 def __reduce__(self):
385 return TTLCache._Link, (self.key, self.expires)
387 def unlink(self):
388 next = self.next
389 prev = self.prev
390 prev.next = next
391 next.prev = prev
393 def __init__(self, maxsize, ttl, timer=time.monotonic, getsizeof=None):
394 _TimedCache.__init__(self, maxsize, timer, getsizeof)
395 self.__root = root = TTLCache._Link()
396 root.prev = root.next = root
397 self.__links = collections.OrderedDict()
398 self.__ttl = ttl
400 def __contains__(self, key):
401 try:
402 link = self.__links[key] # no reordering
403 except KeyError:
404 return False
405 else:
406 return self.timer() < link.expires
408 def __getitem__(self, key, cache_getitem=Cache.__getitem__):
409 try:
410 link = self.__getlink(key)
411 except KeyError:
412 expired = False
413 else:
414 expired = not (self.timer() < link.expires)
415 if expired:
416 return self.__missing__(key)
417 else:
418 return cache_getitem(self, key)
420 def __setitem__(self, key, value, cache_setitem=Cache.__setitem__):
421 with self.timer as time:
422 self.expire(time)
423 cache_setitem(self, key, value)
424 try:
425 link = self.__getlink(key)
426 except KeyError:
427 self.__links[key] = link = TTLCache._Link(key)
428 else:
429 link.unlink()
430 link.expires = time + self.__ttl
431 link.next = root = self.__root
432 link.prev = prev = root.prev
433 prev.next = root.prev = link
435 def __delitem__(self, key, cache_delitem=Cache.__delitem__):
436 cache_delitem(self, key)
437 link = self.__links.pop(key)
438 link.unlink()
439 if not (self.timer() < link.expires):
440 raise KeyError(key)
442 def __iter__(self):
443 root = self.__root
444 curr = root.next
445 while curr is not root:
446 # "freeze" time for iterator access
447 with self.timer as time:
448 if time < curr.expires:
449 yield curr.key
450 curr = curr.next
452 def __setstate__(self, state):
453 self.__dict__.update(state)
454 root = self.__root
455 root.prev = root.next = root
456 for link in sorted(self.__links.values(), key=lambda obj: obj.expires):
457 link.next = root
458 link.prev = prev = root.prev
459 prev.next = root.prev = link
460 self.expire(self.timer())
462 @property
463 def ttl(self):
464 """The time-to-live value of the cache's items."""
465 return self.__ttl
467 def expire(self, time=None):
468 """Remove expired items from the cache."""
469 if time is None:
470 time = self.timer()
471 root = self.__root
472 curr = root.next
473 links = self.__links
474 cache_delitem = Cache.__delitem__
475 while curr is not root and not (time < curr.expires):
476 cache_delitem(self, curr.key)
477 del links[curr.key]
478 next = curr.next
479 curr.unlink()
480 curr = next
482 def popitem(self):
483 """Remove and return the `(key, value)` pair least recently used that
484 has not already expired.
486 """
487 with self.timer as time:
488 self.expire(time)
489 try:
490 key = next(iter(self.__links))
491 except StopIteration:
492 raise KeyError("%s is empty" % type(self).__name__) from None
493 else:
494 return (key, self.pop(key))
496 def __getlink(self, key):
497 value = self.__links[key]
498 self.__links.move_to_end(key)
499 return value
502class TLRUCache(_TimedCache):
503 """Time aware Least Recently Used (TLRU) cache implementation."""
505 @functools.total_ordering
506 class _Item:
508 __slots__ = ("key", "expires", "removed")
510 def __init__(self, key=None, expires=None):
511 self.key = key
512 self.expires = expires
513 self.removed = False
515 def __lt__(self, other):
516 return self.expires < other.expires
518 def __init__(self, maxsize, ttu, timer=time.monotonic, getsizeof=None):
519 _TimedCache.__init__(self, maxsize, timer, getsizeof)
520 self.__items = collections.OrderedDict()
521 self.__order = []
522 self.__ttu = ttu
524 def __contains__(self, key):
525 try:
526 item = self.__items[key] # no reordering
527 except KeyError:
528 return False
529 else:
530 return self.timer() < item.expires
532 def __getitem__(self, key, cache_getitem=Cache.__getitem__):
533 try:
534 item = self.__getitem(key)
535 except KeyError:
536 expired = False
537 else:
538 expired = not (self.timer() < item.expires)
539 if expired:
540 return self.__missing__(key)
541 else:
542 return cache_getitem(self, key)
544 def __setitem__(self, key, value, cache_setitem=Cache.__setitem__):
545 with self.timer as time:
546 expires = self.__ttu(key, value, time)
547 if not (time < expires):
548 return # skip expired items
549 self.expire(time)
550 cache_setitem(self, key, value)
551 # removing an existing item would break the heap structure, so
552 # only mark it as removed for now
553 try:
554 self.__getitem(key).removed = True
555 except KeyError:
556 pass
557 self.__items[key] = item = TLRUCache._Item(key, expires)
558 heapq.heappush(self.__order, item)
560 def __delitem__(self, key, cache_delitem=Cache.__delitem__):
561 with self.timer as time:
562 # no self.expire() for performance reasons, e.g. self.clear() [#67]
563 cache_delitem(self, key)
564 item = self.__items.pop(key)
565 item.removed = True
566 if not (time < item.expires):
567 raise KeyError(key)
569 def __iter__(self):
570 for curr in self.__order:
571 # "freeze" time for iterator access
572 with self.timer as time:
573 if time < curr.expires and not curr.removed:
574 yield curr.key
576 @property
577 def ttu(self):
578 """The local time-to-use function used by the cache."""
579 return self.__ttu
581 def expire(self, time=None):
582 """Remove expired items from the cache."""
583 if time is None:
584 time = self.timer()
585 items = self.__items
586 order = self.__order
587 # clean up the heap if too many items are marked as removed
588 if len(order) > len(items) * 2:
589 self.__order = order = [item for item in order if not item.removed]
590 heapq.heapify(order)
591 cache_delitem = Cache.__delitem__
592 while order and (order[0].removed or not (time < order[0].expires)):
593 item = heapq.heappop(order)
594 if not item.removed:
595 cache_delitem(self, item.key)
596 del items[item.key]
598 def popitem(self):
599 """Remove and return the `(key, value)` pair least recently used that
600 has not already expired.
602 """
603 with self.timer as time:
604 self.expire(time)
605 try:
606 key = next(iter(self.__items))
607 except StopIteration:
608 raise KeyError("%s is empty" % self.__class__.__name__) from None
609 else:
610 return (key, self.pop(key))
612 def __getitem(self, key):
613 value = self.__items[key]
614 self.__items.move_to_end(key)
615 return value
618_CacheInfo = collections.namedtuple(
619 "CacheInfo", ["hits", "misses", "maxsize", "currsize"]
620)
623def cached(cache, key=keys.hashkey, lock=None, info=False):
624 """Decorator to wrap a function with a memoizing callable that saves
625 results in a cache.
627 """
629 def decorator(func):
630 if info:
631 hits = misses = 0
633 if isinstance(cache, Cache):
635 def getinfo():
636 nonlocal hits, misses
637 return _CacheInfo(hits, misses, cache.maxsize, cache.currsize)
639 elif isinstance(cache, collections.abc.Mapping):
641 def getinfo():
642 nonlocal hits, misses
643 return _CacheInfo(hits, misses, None, len(cache))
645 else:
647 def getinfo():
648 nonlocal hits, misses
649 return _CacheInfo(hits, misses, 0, 0)
651 if cache is None:
653 def wrapper(*args, **kwargs):
654 nonlocal misses
655 misses += 1
656 return func(*args, **kwargs)
658 def cache_clear():
659 nonlocal hits, misses
660 hits = misses = 0
662 cache_info = getinfo
664 elif lock is None:
666 def wrapper(*args, **kwargs):
667 nonlocal hits, misses
668 k = key(*args, **kwargs)
669 try:
670 result = cache[k]
671 hits += 1
672 return result
673 except KeyError:
674 misses += 1
675 v = func(*args, **kwargs)
676 try:
677 cache[k] = v
678 except ValueError:
679 pass # value too large
680 return v
682 def cache_clear():
683 nonlocal hits, misses
684 cache.clear()
685 hits = misses = 0
687 cache_info = getinfo
689 else:
691 def wrapper(*args, **kwargs):
692 nonlocal hits, misses
693 k = key(*args, **kwargs)
694 try:
695 with lock:
696 result = cache[k]
697 hits += 1
698 return result
699 except KeyError:
700 with lock:
701 misses += 1
702 v = func(*args, **kwargs)
703 # in case of a race, prefer the item already in the cache
704 try:
705 with lock:
706 return cache.setdefault(k, v)
707 except ValueError:
708 return v # value too large
710 def cache_clear():
711 nonlocal hits, misses
712 with lock:
713 cache.clear()
714 hits = misses = 0
716 def cache_info():
717 with lock:
718 return getinfo()
720 else:
721 if cache is None:
723 def wrapper(*args, **kwargs):
724 return func(*args, **kwargs)
726 def cache_clear():
727 pass
729 elif lock is None:
731 def wrapper(*args, **kwargs):
732 k = key(*args, **kwargs)
733 try:
734 return cache[k]
735 except KeyError:
736 pass # key not found
737 v = func(*args, **kwargs)
738 try:
739 cache[k] = v
740 except ValueError:
741 pass # value too large
742 return v
744 def cache_clear():
745 cache.clear()
747 else:
749 def wrapper(*args, **kwargs):
750 k = key(*args, **kwargs)
751 try:
752 with lock:
753 return cache[k]
754 except KeyError:
755 pass # key not found
756 v = func(*args, **kwargs)
757 # in case of a race, prefer the item already in the cache
758 try:
759 with lock:
760 return cache.setdefault(k, v)
761 except ValueError:
762 return v # value too large
764 def cache_clear():
765 with lock:
766 cache.clear()
768 cache_info = None
770 wrapper.cache = cache
771 wrapper.cache_key = key
772 wrapper.cache_lock = lock
773 wrapper.cache_clear = cache_clear
774 wrapper.cache_info = cache_info
776 return functools.update_wrapper(wrapper, func)
778 return decorator
781def cachedmethod(cache, key=keys.methodkey, lock=None):
782 """Decorator to wrap a class or instance method with a memoizing
783 callable that saves results in a cache.
785 """
787 def decorator(method):
788 if lock is None:
790 def wrapper(self, *args, **kwargs):
791 c = cache(self)
792 if c is None:
793 return method(self, *args, **kwargs)
794 k = key(self, *args, **kwargs)
795 try:
796 return c[k]
797 except KeyError:
798 pass # key not found
799 v = method(self, *args, **kwargs)
800 try:
801 c[k] = v
802 except ValueError:
803 pass # value too large
804 return v
806 def clear(self):
807 c = cache(self)
808 if c is not None:
809 c.clear()
811 else:
813 def wrapper(self, *args, **kwargs):
814 c = cache(self)
815 if c is None:
816 return method(self, *args, **kwargs)
817 k = key(self, *args, **kwargs)
818 try:
819 with lock(self):
820 return c[k]
821 except KeyError:
822 pass # key not found
823 v = method(self, *args, **kwargs)
824 # in case of a race, prefer the item already in the cache
825 try:
826 with lock(self):
827 return c.setdefault(k, v)
828 except ValueError:
829 return v # value too large
831 def clear(self):
832 c = cache(self)
833 if c is not None:
834 with lock(self):
835 c.clear()
837 wrapper.cache = cache
838 wrapper.cache_key = key
839 wrapper.cache_lock = lock
840 wrapper.cache_clear = clear
842 return functools.update_wrapper(wrapper, method)
844 return decorator