Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/cachetools/_cached.py: 49%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""Function decorator helpers."""
3__all__ = ()
5import functools
7# At least for now, the implementation prefers clarity and performance
8# over ease of maintenance, thus providing separate wrappers for
9# all valid combinations of decorator parameters lock, condition and
10# info.
13def _condition_info(func, cache, key, lock, cond, info):
14 hits = misses = 0
15 pending = set()
17 def wrapper(*args, **kwargs):
18 nonlocal hits, misses
19 k = key(*args, **kwargs)
20 with lock:
21 cond.wait_for(lambda: k not in pending)
22 try:
23 result = cache[k]
24 hits += 1
25 return result
26 except KeyError:
27 pending.add(k)
28 misses += 1
29 try:
30 v = func(*args, **kwargs)
31 with lock:
32 try:
33 cache[k] = v
34 except ValueError:
35 pass # value too large
36 return v
37 finally:
38 with lock:
39 pending.remove(k)
40 cond.notify_all()
42 def cache_clear():
43 nonlocal hits, misses
44 with lock:
45 cache.clear()
46 hits = misses = 0
48 def cache_info():
49 with lock:
50 return info(hits, misses)
52 wrapper.cache_clear = cache_clear
53 wrapper.cache_info = cache_info
54 return wrapper
57def _locked_info(func, cache, key, lock, info):
58 hits = misses = 0
60 def wrapper(*args, **kwargs):
61 nonlocal hits, misses
62 k = key(*args, **kwargs)
63 with lock:
64 try:
65 result = cache[k]
66 hits += 1
67 return result
68 except KeyError:
69 misses += 1
70 v = func(*args, **kwargs)
71 with lock:
72 try:
73 # In case of a race condition, i.e. if another thread
74 # stored a value for this key while we were calling
75 # func(), prefer the cached value.
76 return cache.setdefault(k, v)
77 except ValueError:
78 return v # value too large
80 def cache_clear():
81 nonlocal hits, misses
82 with lock:
83 cache.clear()
84 hits = misses = 0
86 def cache_info():
87 with lock:
88 return info(hits, misses)
90 wrapper.cache_clear = cache_clear
91 wrapper.cache_info = cache_info
92 return wrapper
95def _unlocked_info(func, cache, key, info):
96 hits = misses = 0
98 def wrapper(*args, **kwargs):
99 nonlocal hits, misses
100 k = key(*args, **kwargs)
101 try:
102 result = cache[k]
103 hits += 1
104 return result
105 except KeyError:
106 misses += 1
107 v = func(*args, **kwargs)
108 try:
109 cache[k] = v
110 except ValueError:
111 pass # value too large
112 return v
114 def cache_clear():
115 nonlocal hits, misses
116 cache.clear()
117 hits = misses = 0
119 def cache_info():
120 return info(hits, misses)
122 wrapper.cache_clear = cache_clear
123 wrapper.cache_info = cache_info
124 return wrapper
127def _uncached_info(func, info):
128 misses = 0
130 def wrapper(*args, **kwargs):
131 nonlocal misses
132 misses += 1
133 return func(*args, **kwargs)
135 def cache_clear():
136 nonlocal misses
137 misses = 0
139 wrapper.cache_clear = cache_clear
140 wrapper.cache_info = lambda: info(0, misses)
141 return wrapper
144def _condition(func, cache, key, lock, cond):
145 pending = set()
147 def wrapper(*args, **kwargs):
148 k = key(*args, **kwargs)
149 with lock:
150 cond.wait_for(lambda: k not in pending)
151 try:
152 result = cache[k]
153 return result
154 except KeyError:
155 pending.add(k)
156 try:
157 v = func(*args, **kwargs)
158 with lock:
159 try:
160 cache[k] = v
161 except ValueError:
162 pass # value too large
163 return v
164 finally:
165 with lock:
166 pending.remove(k)
167 cond.notify_all()
169 def cache_clear():
170 with lock:
171 cache.clear()
173 wrapper.cache_clear = cache_clear
174 return wrapper
177def _locked(func, cache, key, lock):
178 def wrapper(*args, **kwargs):
179 k = key(*args, **kwargs)
180 with lock:
181 try:
182 return cache[k]
183 except KeyError:
184 pass # key not found
185 v = func(*args, **kwargs)
186 with lock:
187 try:
188 # In case of a race condition, i.e. if another thread
189 # stored a value for this key while we were calling
190 # func(), prefer the cached value.
191 return cache.setdefault(k, v)
192 except ValueError:
193 return v # value too large
195 def cache_clear():
196 with lock:
197 cache.clear()
199 wrapper.cache_clear = cache_clear
200 return wrapper
203def _unlocked(func, cache, key):
204 def wrapper(*args, **kwargs):
205 k = key(*args, **kwargs)
206 try:
207 return cache[k]
208 except KeyError:
209 pass # key not found
210 v = func(*args, **kwargs)
211 try:
212 cache[k] = v
213 except ValueError:
214 pass # value too large
215 return v
217 wrapper.cache_clear = lambda: cache.clear()
218 return wrapper
221def _uncached(func):
222 def wrapper(*args, **kwargs):
223 return func(*args, **kwargs)
225 wrapper.cache_clear = lambda: None
226 return wrapper
229def _wrapper(func, cache, key, lock=None, cond=None, info=None):
230 if info is not None:
231 if cache is None:
232 wrapper = _uncached_info(func, info)
233 elif cond is not None and lock is not None:
234 wrapper = _condition_info(func, cache, key, lock, cond, info)
235 elif cond is not None:
236 wrapper = _condition_info(func, cache, key, cond, cond, info)
237 elif lock is not None:
238 wrapper = _locked_info(func, cache, key, lock, info)
239 else:
240 wrapper = _unlocked_info(func, cache, key, info)
241 else:
242 if cache is None:
243 wrapper = _uncached(func)
244 elif cond is not None and lock is not None:
245 wrapper = _condition(func, cache, key, lock, cond)
246 elif cond is not None:
247 wrapper = _condition(func, cache, key, cond, cond)
248 elif lock is not None:
249 wrapper = _locked(func, cache, key, lock)
250 else:
251 wrapper = _unlocked(func, cache, key)
252 wrapper.cache_info = None
254 wrapper.cache = cache
255 wrapper.cache_key = key
256 wrapper.cache_lock = lock if lock is not None else cond
257 wrapper.cache_condition = cond
259 return functools.update_wrapper(wrapper, func)