1"""Function decorator helpers."""
2
3import functools
4
5
6def _condition_info(func, cache, key, lock, cond, info):
7 hits = misses = 0
8 pending = set()
9
10 def wrapper(*args, **kwargs):
11 nonlocal hits, misses
12 k = key(*args, **kwargs)
13 with lock:
14 cond.wait_for(lambda: k not in pending)
15 try:
16 result = cache[k]
17 hits += 1
18 return result
19 except KeyError:
20 pending.add(k)
21 misses += 1
22 try:
23 v = func(*args, **kwargs)
24 with lock:
25 try:
26 cache[k] = v
27 except ValueError:
28 pass # value too large
29 return v
30 finally:
31 with lock:
32 pending.remove(k)
33 cond.notify_all()
34
35 def cache_clear():
36 nonlocal hits, misses
37 with lock:
38 cache.clear()
39 hits = misses = 0
40
41 def cache_info():
42 with lock:
43 return info(hits, misses)
44
45 wrapper.cache_clear = cache_clear
46 wrapper.cache_info = cache_info
47 return wrapper
48
49
50def _locked_info(func, cache, key, lock, info):
51 hits = misses = 0
52
53 def wrapper(*args, **kwargs):
54 nonlocal hits, misses
55 k = key(*args, **kwargs)
56 with lock:
57 try:
58 result = cache[k]
59 hits += 1
60 return result
61 except KeyError:
62 misses += 1
63 v = func(*args, **kwargs)
64 with lock:
65 try:
66 # in case of a race, prefer the item already in the cache
67 return cache.setdefault(k, v)
68 except ValueError:
69 return v # value too large
70
71 def cache_clear():
72 nonlocal hits, misses
73 with lock:
74 cache.clear()
75 hits = misses = 0
76
77 def cache_info():
78 with lock:
79 return info(hits, misses)
80
81 wrapper.cache_clear = cache_clear
82 wrapper.cache_info = cache_info
83 return wrapper
84
85
86def _unlocked_info(func, cache, key, info):
87 hits = misses = 0
88
89 def wrapper(*args, **kwargs):
90 nonlocal hits, misses
91 k = key(*args, **kwargs)
92 try:
93 result = cache[k]
94 hits += 1
95 return result
96 except KeyError:
97 misses += 1
98 v = func(*args, **kwargs)
99 try:
100 cache[k] = v
101 except ValueError:
102 pass # value too large
103 return v
104
105 def cache_clear():
106 nonlocal hits, misses
107 cache.clear()
108 hits = misses = 0
109
110 wrapper.cache_clear = cache_clear
111 wrapper.cache_info = lambda: info(hits, misses)
112 return wrapper
113
114
115def _uncached_info(func, info):
116 misses = 0
117
118 def wrapper(*args, **kwargs):
119 nonlocal misses
120 misses += 1
121 return func(*args, **kwargs)
122
123 def cache_clear():
124 nonlocal misses
125 misses = 0
126
127 wrapper.cache_clear = cache_clear
128 wrapper.cache_info = lambda: info(0, misses)
129 return wrapper
130
131
132def _condition(func, cache, key, lock, cond):
133 pending = set()
134
135 def wrapper(*args, **kwargs):
136 k = key(*args, **kwargs)
137 with lock:
138 cond.wait_for(lambda: k not in pending)
139 try:
140 result = cache[k]
141 return result
142 except KeyError:
143 pending.add(k)
144 try:
145 v = func(*args, **kwargs)
146 with lock:
147 try:
148 cache[k] = v
149 except ValueError:
150 pass # value too large
151 return v
152 finally:
153 with lock:
154 pending.remove(k)
155 cond.notify_all()
156
157 def cache_clear():
158 with lock:
159 cache.clear()
160
161 wrapper.cache_clear = cache_clear
162 return wrapper
163
164
165def _locked(func, cache, key, lock):
166 def wrapper(*args, **kwargs):
167 k = key(*args, **kwargs)
168 with lock:
169 try:
170 return cache[k]
171 except KeyError:
172 pass # key not found
173 v = func(*args, **kwargs)
174 with lock:
175 try:
176 # in case of a race, prefer the item already in the cache
177 return cache.setdefault(k, v)
178 except ValueError:
179 return v # value too large
180
181 def cache_clear():
182 with lock:
183 cache.clear()
184
185 wrapper.cache_clear = cache_clear
186 return wrapper
187
188
189def _unlocked(func, cache, key):
190 def wrapper(*args, **kwargs):
191 k = key(*args, **kwargs)
192 try:
193 return cache[k]
194 except KeyError:
195 pass # key not found
196 v = func(*args, **kwargs)
197 try:
198 cache[k] = v
199 except ValueError:
200 pass # value too large
201 return v
202
203 wrapper.cache_clear = lambda: cache.clear()
204 return wrapper
205
206
207def _uncached(func):
208 def wrapper(*args, **kwargs):
209 return func(*args, **kwargs)
210
211 wrapper.cache_clear = lambda: None
212 return wrapper
213
214
215def _wrapper(func, cache, key, lock=None, cond=None, info=None):
216 if info is not None:
217 if cache is None:
218 wrapper = _uncached_info(func, info)
219 elif cond is not None and lock is not None:
220 wrapper = _condition_info(func, cache, key, lock, cond, info)
221 elif cond is not None:
222 wrapper = _condition_info(func, cache, key, cond, cond, info)
223 elif lock is not None:
224 wrapper = _locked_info(func, cache, key, lock, info)
225 else:
226 wrapper = _unlocked_info(func, cache, key, info)
227 else:
228 if cache is None:
229 wrapper = _uncached(func)
230 elif cond is not None and lock is not None:
231 wrapper = _condition(func, cache, key, lock, cond)
232 elif cond is not None:
233 wrapper = _condition(func, cache, key, cond, cond)
234 elif lock is not None:
235 wrapper = _locked(func, cache, key, lock)
236 else:
237 wrapper = _unlocked(func, cache, key)
238 wrapper.cache_info = None
239
240 wrapper.cache = cache
241 wrapper.cache_key = key
242 wrapper.cache_lock = lock if lock is not None else cond
243 wrapper.cache_condition = cond
244
245 return functools.update_wrapper(wrapper, func)