cachetools 7.2.6__tar.gz → 7.2.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cachetools-7.2.6 → cachetools-7.2.7}/CHANGELOG.rst +8 -0
- {cachetools-7.2.6/src/cachetools.egg-info → cachetools-7.2.7}/PKG-INFO +1 -1
- {cachetools-7.2.6 → cachetools-7.2.7}/src/cachetools/__init__.py +58 -60
- {cachetools-7.2.6 → cachetools-7.2.7}/src/cachetools/_descriptor.py +43 -37
- {cachetools-7.2.6 → cachetools-7.2.7/src/cachetools.egg-info}/PKG-INFO +1 -1
- {cachetools-7.2.6 → cachetools-7.2.7}/tests/test_cachedmethod.py +73 -0
- {cachetools-7.2.6 → cachetools-7.2.7}/tests/test_classmethod.py +43 -0
- {cachetools-7.2.6 → cachetools-7.2.7}/LICENSE +0 -0
- {cachetools-7.2.6 → cachetools-7.2.7}/MANIFEST.in +0 -0
- {cachetools-7.2.6 → cachetools-7.2.7}/README.rst +0 -0
- {cachetools-7.2.6 → cachetools-7.2.7}/docs/conf.py +0 -0
- {cachetools-7.2.6 → cachetools-7.2.7}/docs/index.rst +0 -0
- {cachetools-7.2.6 → cachetools-7.2.7}/pyproject.toml +0 -0
- {cachetools-7.2.6 → cachetools-7.2.7}/setup.cfg +0 -0
- {cachetools-7.2.6 → cachetools-7.2.7}/src/cachetools/__init__.pyi +0 -0
- {cachetools-7.2.6 → cachetools-7.2.7}/src/cachetools/_cached.py +0 -0
- {cachetools-7.2.6 → cachetools-7.2.7}/src/cachetools/_cachedmethod.py +0 -0
- {cachetools-7.2.6 → cachetools-7.2.7}/src/cachetools/func.py +0 -0
- {cachetools-7.2.6 → cachetools-7.2.7}/src/cachetools/func.pyi +0 -0
- {cachetools-7.2.6 → cachetools-7.2.7}/src/cachetools/keys.py +0 -0
- {cachetools-7.2.6 → cachetools-7.2.7}/src/cachetools/keys.pyi +0 -0
- {cachetools-7.2.6 → cachetools-7.2.7}/src/cachetools/py.typed +0 -0
- {cachetools-7.2.6 → cachetools-7.2.7}/src/cachetools.egg-info/SOURCES.txt +0 -0
- {cachetools-7.2.6 → cachetools-7.2.7}/src/cachetools.egg-info/dependency_links.txt +0 -0
- {cachetools-7.2.6 → cachetools-7.2.7}/src/cachetools.egg-info/top_level.txt +0 -0
- {cachetools-7.2.6 → cachetools-7.2.7}/tests/__init__.py +0 -0
- {cachetools-7.2.6 → cachetools-7.2.7}/tests/test_cache.py +0 -0
- {cachetools-7.2.6 → cachetools-7.2.7}/tests/test_cached.py +0 -0
- {cachetools-7.2.6 → cachetools-7.2.7}/tests/test_fifo.py +0 -0
- {cachetools-7.2.6 → cachetools-7.2.7}/tests/test_func.py +0 -0
- {cachetools-7.2.6 → cachetools-7.2.7}/tests/test_keys.py +0 -0
- {cachetools-7.2.6 → cachetools-7.2.7}/tests/test_lfu.py +0 -0
- {cachetools-7.2.6 → cachetools-7.2.7}/tests/test_lru.py +0 -0
- {cachetools-7.2.6 → cachetools-7.2.7}/tests/test_rr.py +0 -0
- {cachetools-7.2.6 → cachetools-7.2.7}/tests/test_threading.py +0 -0
- {cachetools-7.2.6 → cachetools-7.2.7}/tests/test_tlru.py +0 -0
- {cachetools-7.2.6 → cachetools-7.2.7}/tests/test_ttl.py +0 -0
- {cachetools-7.2.6 → cachetools-7.2.7}/tox.ini +0 -0
|
@@ -12,7 +12,7 @@ __all__ = (
|
|
|
12
12
|
"cachedmethod",
|
|
13
13
|
)
|
|
14
14
|
|
|
15
|
-
__version__ = "7.2.
|
|
15
|
+
__version__ = "7.2.7"
|
|
16
16
|
|
|
17
17
|
import collections
|
|
18
18
|
import collections.abc
|
|
@@ -24,30 +24,12 @@ import time
|
|
|
24
24
|
from . import keys
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
class _DefaultSize:
|
|
28
|
-
"""A minimal "fake" dict that returns a constant size 1 for any key."""
|
|
29
|
-
|
|
30
|
-
__slots__ = ()
|
|
31
|
-
|
|
32
|
-
def __getitem__(self, _key):
|
|
33
|
-
return 1
|
|
34
|
-
|
|
35
|
-
def __setitem__(self, _key, _value):
|
|
36
|
-
pass
|
|
37
|
-
|
|
38
|
-
def pop(self, _key):
|
|
39
|
-
return 1
|
|
40
|
-
|
|
41
|
-
def clear(self):
|
|
42
|
-
pass
|
|
43
|
-
|
|
44
|
-
|
|
45
27
|
class Cache(collections.abc.MutableMapping):
|
|
46
28
|
"""Mutable mapping to serve as a simple cache or cache base class."""
|
|
47
29
|
|
|
48
30
|
__marker = object()
|
|
49
31
|
|
|
50
|
-
__size =
|
|
32
|
+
__size = None
|
|
51
33
|
|
|
52
34
|
def __init__(self, maxsize, getsizeof=None):
|
|
53
35
|
if maxsize < 0:
|
|
@@ -75,30 +57,48 @@ class Cache(collections.abc.MutableMapping):
|
|
|
75
57
|
return self.__missing__(key)
|
|
76
58
|
|
|
77
59
|
def __setitem__(self, key, value):
|
|
60
|
+
data = self.__data
|
|
61
|
+
size = self.__size
|
|
78
62
|
maxsize = self.__maxsize
|
|
79
|
-
size
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
63
|
+
if size is None:
|
|
64
|
+
if maxsize < 1:
|
|
65
|
+
raise ValueError("value too large")
|
|
66
|
+
elif key in data:
|
|
67
|
+
data[key] = value
|
|
68
|
+
else:
|
|
69
|
+
while self.__currsize + 1 > maxsize:
|
|
70
|
+
self.popitem()
|
|
71
|
+
data[key] = value
|
|
72
|
+
self.__currsize += 1
|
|
88
73
|
else:
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
74
|
+
itemsize = self.getsizeof(value)
|
|
75
|
+
if itemsize > maxsize:
|
|
76
|
+
raise ValueError("value too large")
|
|
77
|
+
if itemsize < 0:
|
|
78
|
+
raise ValueError("value size must be non-negative")
|
|
79
|
+
if key not in data:
|
|
80
|
+
diffsize = itemsize
|
|
81
|
+
while self.__currsize + diffsize > maxsize:
|
|
82
|
+
self.popitem()
|
|
83
|
+
else:
|
|
84
|
+
diffsize = itemsize - size[key]
|
|
85
|
+
while self.__currsize + diffsize > maxsize:
|
|
86
|
+
self.popitem()
|
|
87
|
+
if key not in data:
|
|
88
|
+
diffsize = itemsize
|
|
89
|
+
data[key] = value
|
|
90
|
+
size[key] = itemsize
|
|
91
|
+
self.__currsize += diffsize
|
|
97
92
|
|
|
98
93
|
def __delitem__(self, key):
|
|
99
|
-
size = self.__size
|
|
100
|
-
|
|
101
|
-
|
|
94
|
+
size = self.__size
|
|
95
|
+
if size is None:
|
|
96
|
+
del self.__data[key]
|
|
97
|
+
self.__currsize -= 1
|
|
98
|
+
else:
|
|
99
|
+
itemsize = size.pop(key)
|
|
100
|
+
del self.__data[key]
|
|
101
|
+
self.__currsize -= itemsize
|
|
102
102
|
|
|
103
103
|
def __contains__(self, key):
|
|
104
104
|
return key in self.__data
|
|
@@ -148,8 +148,10 @@ class Cache(collections.abc.MutableMapping):
|
|
|
148
148
|
# optimized version for each Cache subclass.
|
|
149
149
|
|
|
150
150
|
def clear(self):
|
|
151
|
+
size = self.__size
|
|
152
|
+
if size is not None:
|
|
153
|
+
size.clear()
|
|
151
154
|
self.__data.clear()
|
|
152
|
-
self.__size.clear()
|
|
153
155
|
self.__currsize = 0
|
|
154
156
|
|
|
155
157
|
@property
|
|
@@ -508,11 +510,12 @@ class TTLCache(_TimedCache):
|
|
|
508
510
|
expires = time + self.__ttl
|
|
509
511
|
self.expire(time)
|
|
510
512
|
cache_setitem(self, key, value)
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
513
|
+
links = self.__links
|
|
514
|
+
link = links.get(key)
|
|
515
|
+
if link is None:
|
|
516
|
+
links[key] = link = TTLCache._Link(key)
|
|
515
517
|
else:
|
|
518
|
+
links.move_to_end(key)
|
|
516
519
|
link.unlink()
|
|
517
520
|
link.expires = expires
|
|
518
521
|
link.next = root = self.__root
|
|
@@ -560,6 +563,8 @@ class TTLCache(_TimedCache):
|
|
|
560
563
|
time = self.timer()
|
|
561
564
|
root = self.__root
|
|
562
565
|
curr = root.next
|
|
566
|
+
if curr is root or time < curr.expires:
|
|
567
|
+
return [] # fast path: nothing has expired
|
|
563
568
|
links = self.__links
|
|
564
569
|
expired = []
|
|
565
570
|
cache_delitem = Cache.__delitem__
|
|
@@ -595,11 +600,6 @@ class TTLCache(_TimedCache):
|
|
|
595
600
|
root.prev = root.next = root
|
|
596
601
|
self.__links.clear()
|
|
597
602
|
|
|
598
|
-
def __getlink(self, key):
|
|
599
|
-
value = self.__links[key]
|
|
600
|
-
self.__links.move_to_end(key)
|
|
601
|
-
return value
|
|
602
|
-
|
|
603
603
|
|
|
604
604
|
class TLRUCache(_TimedCache):
|
|
605
605
|
"""Time-aware Least Recently Used (TLRU) cache implementation."""
|
|
@@ -655,11 +655,12 @@ class TLRUCache(_TimedCache):
|
|
|
655
655
|
cache_setitem(self, key, value)
|
|
656
656
|
# removing an existing item would break the heap structure, so
|
|
657
657
|
# only mark it as removed for now
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
658
|
+
items = self.__items
|
|
659
|
+
item = items.get(key)
|
|
660
|
+
if item is not None:
|
|
661
|
+
item.removed = True
|
|
662
|
+
items.move_to_end(key)
|
|
663
|
+
items[key] = item = TLRUCache._Item(key, expires)
|
|
663
664
|
heapq.heappush(self.__order, item)
|
|
664
665
|
|
|
665
666
|
def __delitem__(self, key, cache_delitem=Cache.__delitem__):
|
|
@@ -696,6 +697,8 @@ class TLRUCache(_TimedCache):
|
|
|
696
697
|
if len(order) > len(items) * self.__HEAP_CLEANUP_FACTOR:
|
|
697
698
|
self.__order = order = [item for item in order if not item.removed]
|
|
698
699
|
heapq.heapify(order)
|
|
700
|
+
if not order or (not order[0].removed and time < order[0].expires):
|
|
701
|
+
return [] # fast path: nothing has expired
|
|
699
702
|
expired = []
|
|
700
703
|
cache_delitem = Cache.__delitem__
|
|
701
704
|
cache_getitem = Cache.__getitem__
|
|
@@ -728,11 +731,6 @@ class TLRUCache(_TimedCache):
|
|
|
728
731
|
self.__items.clear()
|
|
729
732
|
del self.__order[:]
|
|
730
733
|
|
|
731
|
-
def __getitem(self, key):
|
|
732
|
-
value = self.__items[key]
|
|
733
|
-
self.__items.move_to_end(key)
|
|
734
|
-
return value
|
|
735
|
-
|
|
736
734
|
def __delitem(self, key, cache_delitem=Cache.__delitem__):
|
|
737
735
|
try:
|
|
738
736
|
self.__items.pop(key).removed = True
|
|
@@ -46,46 +46,34 @@ class _MethodDescriptor:
|
|
|
46
46
|
# objtype is not used for instance lookups, so it need not be bound here
|
|
47
47
|
return self.__get_unbound()
|
|
48
48
|
elif self.__attrname is not None:
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
# the descriptor, prefer the initial wrapper.
|
|
49
|
+
descriptor = next(
|
|
50
|
+
(
|
|
51
|
+
cls.__dict__[self.__attrname]
|
|
52
|
+
for cls in type(obj).__mro__
|
|
53
|
+
if self.__attrname in cls.__dict__
|
|
54
|
+
),
|
|
55
|
+
None,
|
|
56
|
+
)
|
|
57
|
+
if descriptor is self:
|
|
58
|
+
# In case of a race condition where another thread already replaced
|
|
59
|
+
# the descriptor, prefer the initial wrapper.
|
|
60
|
+
wrapper = self.Wrapper(obj, self) # type: ignore
|
|
61
|
+
try:
|
|
63
62
|
wrapper = obj.__dict__.setdefault(self.__attrname, wrapper)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
except (AttributeError, TypeError) as e:
|
|
64
|
+
self.__instance_dict_error(obj, e)
|
|
65
|
+
else:
|
|
66
|
+
# A super() lookup must neither reuse the override's wrapper
|
|
67
|
+
# nor replace it, so preserve state separately for each descriptor.
|
|
68
|
+
try:
|
|
67
69
|
wrappers = obj.__dict__.setdefault("__cachetools_cachedmethods", {})
|
|
70
|
+
except (AttributeError, TypeError) as e:
|
|
71
|
+
self.__instance_dict_error(obj, e)
|
|
72
|
+
wrappers = {} # deprecated fallback: throwaway wrapper
|
|
73
|
+
wrapper = wrappers.get(self)
|
|
74
|
+
if wrapper is None:
|
|
75
|
+
wrapper = self.Wrapper(obj, self) # type: ignore
|
|
68
76
|
wrapper = wrappers.setdefault(self, wrapper)
|
|
69
|
-
except AttributeError:
|
|
70
|
-
# not all objects have __dict__ (e.g. class defines slots)
|
|
71
|
-
msg = (
|
|
72
|
-
f"No '__dict__' attribute on {type(obj).__name__!r} "
|
|
73
|
-
f"instance to cache {self.__attrname!r} property."
|
|
74
|
-
)
|
|
75
|
-
if self.__deprecated:
|
|
76
|
-
_warn_instance_dict(msg, 3)
|
|
77
|
-
else:
|
|
78
|
-
raise TypeError(msg) from None
|
|
79
|
-
except TypeError:
|
|
80
|
-
msg = (
|
|
81
|
-
f"The '__dict__' attribute on {type(obj).__name__!r} "
|
|
82
|
-
f"instance does not support item assignment for "
|
|
83
|
-
f"caching {self.__attrname!r} property."
|
|
84
|
-
)
|
|
85
|
-
if self.__deprecated:
|
|
86
|
-
_warn_instance_dict(msg, 3)
|
|
87
|
-
else:
|
|
88
|
-
raise TypeError(msg) from None
|
|
89
77
|
elif self.__deprecated:
|
|
90
78
|
# deprecated @classmethod, warning already raised elsewhere
|
|
91
79
|
wrapper = self.Wrapper(obj, self) # type: ignore
|
|
@@ -110,6 +98,24 @@ class _MethodDescriptor:
|
|
|
110
98
|
self.__unbound = _Unbound(self)
|
|
111
99
|
return self.__unbound
|
|
112
100
|
|
|
101
|
+
def __instance_dict_error(self, obj, error):
|
|
102
|
+
if isinstance(error, AttributeError):
|
|
103
|
+
# not all objects have __dict__ (e.g. class defines slots)
|
|
104
|
+
msg = (
|
|
105
|
+
f"No '__dict__' attribute on {type(obj).__name__!r} "
|
|
106
|
+
f"instance to cache {self.__attrname!r}"
|
|
107
|
+
)
|
|
108
|
+
else:
|
|
109
|
+
# some objects may have an immutable __dict__
|
|
110
|
+
msg = (
|
|
111
|
+
f"The '__dict__' attribute on {type(obj).__name__!r} "
|
|
112
|
+
f"instance does not support item assignment for "
|
|
113
|
+
f"caching {self.__attrname!r}"
|
|
114
|
+
)
|
|
115
|
+
if not self.__deprecated:
|
|
116
|
+
raise TypeError(msg) from None
|
|
117
|
+
_warn_instance_dict(msg, 4)
|
|
118
|
+
|
|
113
119
|
|
|
114
120
|
class _Unbound:
|
|
115
121
|
"""Callable returned on class access.
|
|
@@ -565,6 +565,15 @@ class MethodDecoratorTestMixin(SimplifiedTestCaseProtocol):
|
|
|
565
565
|
def get_info(self, value):
|
|
566
566
|
return value
|
|
567
567
|
|
|
568
|
+
class SlotsChild(Slots):
|
|
569
|
+
__slots__ = ()
|
|
570
|
+
|
|
571
|
+
def get(self, value): # type: ignore
|
|
572
|
+
return super().get(value)
|
|
573
|
+
|
|
574
|
+
def get_info(self, value): # type: ignore
|
|
575
|
+
return super().get_info(value)
|
|
576
|
+
|
|
568
577
|
cache = self.cache(2)
|
|
569
578
|
cached = Slots(cache)
|
|
570
579
|
|
|
@@ -579,6 +588,21 @@ class MethodDecoratorTestMixin(SimplifiedTestCaseProtocol):
|
|
|
579
588
|
self.assertEqual(len(w), 2)
|
|
580
589
|
self.assertIs(w[0].category, DeprecationWarning)
|
|
581
590
|
self.assertIs(w[1].category, DeprecationWarning)
|
|
591
|
+
self.assertEqual(w[0].filename, __file__)
|
|
592
|
+
|
|
593
|
+
child = SlotsChild(cache)
|
|
594
|
+
|
|
595
|
+
with self.assertRaisesRegex(TypeError, "No '__dict__' attribute"):
|
|
596
|
+
child.get_info(42)
|
|
597
|
+
|
|
598
|
+
with warnings.catch_warnings(record=True) as w:
|
|
599
|
+
warnings.simplefilter("always")
|
|
600
|
+
self.assertEqual(child.get(0), 0)
|
|
601
|
+
self.assertEqual(child.get(0), 0)
|
|
602
|
+
self.assertEqual(len(w), 2)
|
|
603
|
+
self.assertIs(w[0].category, DeprecationWarning)
|
|
604
|
+
self.assertIs(w[1].category, DeprecationWarning)
|
|
605
|
+
self.assertEqual(w[0].filename, __file__)
|
|
582
606
|
|
|
583
607
|
def test_decorator_immutable_dict(self):
|
|
584
608
|
|
|
@@ -602,6 +626,13 @@ class MethodDecoratorTestMixin(SimplifiedTestCaseProtocol):
|
|
|
602
626
|
def get_info(self, value):
|
|
603
627
|
return value
|
|
604
628
|
|
|
629
|
+
class ImmutableChild(Immutable):
|
|
630
|
+
def get(self, value): # type: ignore
|
|
631
|
+
return super().get(value)
|
|
632
|
+
|
|
633
|
+
def get_info(self, value): # type: ignore
|
|
634
|
+
return super().get_info(value)
|
|
635
|
+
|
|
605
636
|
cache = self.cache(2)
|
|
606
637
|
cached = Immutable(cache)
|
|
607
638
|
|
|
@@ -616,6 +647,48 @@ class MethodDecoratorTestMixin(SimplifiedTestCaseProtocol):
|
|
|
616
647
|
self.assertEqual(len(w), 2)
|
|
617
648
|
self.assertIs(w[0].category, DeprecationWarning)
|
|
618
649
|
self.assertIs(w[1].category, DeprecationWarning)
|
|
650
|
+
self.assertEqual(w[0].filename, __file__)
|
|
651
|
+
|
|
652
|
+
child = ImmutableChild(cache)
|
|
653
|
+
|
|
654
|
+
with self.assertRaisesRegex(TypeError, "does not support item assignment"):
|
|
655
|
+
child.get_info(42)
|
|
656
|
+
|
|
657
|
+
with warnings.catch_warnings(record=True) as w:
|
|
658
|
+
warnings.simplefilter("always")
|
|
659
|
+
self.assertEqual(child.get(0), 0)
|
|
660
|
+
self.assertEqual(child.get(0), 0)
|
|
661
|
+
self.assertEqual(len(w), 2)
|
|
662
|
+
self.assertIs(w[0].category, DeprecationWarning)
|
|
663
|
+
self.assertIs(w[1].category, DeprecationWarning)
|
|
664
|
+
self.assertEqual(w[0].filename, __file__)
|
|
665
|
+
|
|
666
|
+
def test_decorator_wrapper_error(self):
|
|
667
|
+
# a non-callable key makes Wrapper.__init__ raise TypeError
|
|
668
|
+
class Cached:
|
|
669
|
+
def __init__(self, cache):
|
|
670
|
+
self.cache = cache
|
|
671
|
+
|
|
672
|
+
@cachedmethod(lambda self: self.cache, key=None) # type: ignore
|
|
673
|
+
def get(self, value):
|
|
674
|
+
return value
|
|
675
|
+
|
|
676
|
+
@cachedmethod(lambda self: self.cache, key=None, info=True) # type: ignore
|
|
677
|
+
def get_info(self, value):
|
|
678
|
+
return value
|
|
679
|
+
|
|
680
|
+
class CachedChild(Cached):
|
|
681
|
+
def get(self, value): # type: ignore
|
|
682
|
+
return super().get(value)
|
|
683
|
+
|
|
684
|
+
def get_info(self, value):
|
|
685
|
+
return super().get_info(value)
|
|
686
|
+
|
|
687
|
+
cached = CachedChild(self.cache(2))
|
|
688
|
+
with self.assertRaisesRegex(TypeError, "must be callable"):
|
|
689
|
+
cached.get(0)
|
|
690
|
+
with self.assertRaisesRegex(TypeError, "must be callable"):
|
|
691
|
+
cached.get_info(0)
|
|
619
692
|
|
|
620
693
|
def test_decorator_different_names(self):
|
|
621
694
|
# RuntimeError for Python < 3.12, TypeError otherwise
|
|
@@ -36,6 +36,12 @@ class Cached:
|
|
|
36
36
|
cls.count += 1
|
|
37
37
|
return cls.count
|
|
38
38
|
|
|
39
|
+
@classmethod
|
|
40
|
+
@cachedmethod(lambda cls: cls.cache, info=True)
|
|
41
|
+
def get_info(cls, value):
|
|
42
|
+
cls.count += 1
|
|
43
|
+
return cls.count
|
|
44
|
+
|
|
39
45
|
|
|
40
46
|
class CachedClassMethodTest(unittest.TestCase):
|
|
41
47
|
def test(self):
|
|
@@ -114,6 +120,43 @@ class CachedClassMethodTest(unittest.TestCase):
|
|
|
114
120
|
self.assertIs(w[0].category, DeprecationWarning)
|
|
115
121
|
self.assertIs(w[1].category, DeprecationWarning)
|
|
116
122
|
|
|
123
|
+
def test_info(self):
|
|
124
|
+
# info=True requires an instance method, so calls fail on all
|
|
125
|
+
# supported Python versions
|
|
126
|
+
Cached.cache = LRUCache(2)
|
|
127
|
+
cached = Cached()
|
|
128
|
+
|
|
129
|
+
with warnings.catch_warnings():
|
|
130
|
+
warnings.simplefilter("ignore")
|
|
131
|
+
with self.assertRaises(TypeError):
|
|
132
|
+
cached.get_info(0)
|
|
133
|
+
with self.assertRaises(TypeError):
|
|
134
|
+
Cached.get_info(0)
|
|
135
|
+
self.assertEqual(len(Cached.cache), 0)
|
|
136
|
+
|
|
137
|
+
def test_shared_cache(self):
|
|
138
|
+
class Child(Cached):
|
|
139
|
+
pass
|
|
140
|
+
|
|
141
|
+
# cache is a class attribute, so it is shared with subclasses
|
|
142
|
+
Cached.cache = LRUCache(2)
|
|
143
|
+
Cached.count = 0
|
|
144
|
+
|
|
145
|
+
with warnings.catch_warnings():
|
|
146
|
+
warnings.simplefilter("ignore")
|
|
147
|
+
self.assertIs(Child.cache, Cached.cache)
|
|
148
|
+
self.assertEqual(Cached.get(0), 1)
|
|
149
|
+
self.assertEqual(Child.get(0), 1)
|
|
150
|
+
self.assertEqual(Child().get(0), 1)
|
|
151
|
+
self.assertEqual(len(Cached.cache), 1)
|
|
152
|
+
self.assertEqual(Child.get(1), 2)
|
|
153
|
+
self.assertEqual(Cached.get(1), 2)
|
|
154
|
+
self.assertEqual(len(Cached.cache), 2)
|
|
155
|
+
|
|
156
|
+
Child.get.cache_clear(Child) # type: ignore
|
|
157
|
+
self.assertEqual(len(Cached.cache), 0)
|
|
158
|
+
self.assertEqual(Cached.get(0), 2)
|
|
159
|
+
|
|
117
160
|
def test_clear(self):
|
|
118
161
|
Cached.cache = LRUCache(2)
|
|
119
162
|
Cached.count = 0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|