129 lines
3.3 KiB
Python
129 lines
3.3 KiB
Python
![]() |
"""Method decorator helpers."""
|
||
|
|
||
|
import functools
|
||
|
import weakref
|
||
|
|
||
|
|
||
|
def warn_cache_none():
|
||
|
from warnings import warn
|
||
|
|
||
|
warn(
|
||
|
"returning `None` from `cache(self)` is deprecated",
|
||
|
DeprecationWarning,
|
||
|
stacklevel=3,
|
||
|
)
|
||
|
|
||
|
|
||
|
def _condition(method, cache, key, lock, cond):
|
||
|
pending = weakref.WeakKeyDictionary()
|
||
|
|
||
|
def wrapper(self, *args, **kwargs):
|
||
|
c = cache(self)
|
||
|
if c is None:
|
||
|
warn_cache_none()
|
||
|
return method(self, *args, **kwargs)
|
||
|
k = key(self, *args, **kwargs)
|
||
|
with lock(self):
|
||
|
p = pending.setdefault(self, set())
|
||
|
cond(self).wait_for(lambda: k not in p)
|
||
|
try:
|
||
|
return c[k]
|
||
|
except KeyError:
|
||
|
p.add(k)
|
||
|
try:
|
||
|
v = method(self, *args, **kwargs)
|
||
|
with lock(self):
|
||
|
try:
|
||
|
c[k] = v
|
||
|
except ValueError:
|
||
|
pass # value too large
|
||
|
return v
|
||
|
finally:
|
||
|
with lock(self):
|
||
|
pending[self].remove(k)
|
||
|
cond(self).notify_all()
|
||
|
|
||
|
def cache_clear(self):
|
||
|
c = cache(self)
|
||
|
if c is not None:
|
||
|
with lock(self):
|
||
|
c.clear()
|
||
|
|
||
|
wrapper.cache_clear = cache_clear
|
||
|
return wrapper
|
||
|
|
||
|
|
||
|
def _locked(method, cache, key, lock):
|
||
|
def wrapper(self, *args, **kwargs):
|
||
|
c = cache(self)
|
||
|
if c is None:
|
||
|
warn_cache_none()
|
||
|
return method(self, *args, **kwargs)
|
||
|
k = key(self, *args, **kwargs)
|
||
|
with lock(self):
|
||
|
try:
|
||
|
return c[k]
|
||
|
except KeyError:
|
||
|
pass # key not found
|
||
|
v = method(self, *args, **kwargs)
|
||
|
# in case of a race, prefer the item already in the cache
|
||
|
with lock(self):
|
||
|
try:
|
||
|
return c.setdefault(k, v)
|
||
|
except ValueError:
|
||
|
return v # value too large
|
||
|
|
||
|
def cache_clear(self):
|
||
|
c = cache(self)
|
||
|
if c is not None:
|
||
|
with lock(self):
|
||
|
c.clear()
|
||
|
|
||
|
wrapper.cache_clear = cache_clear
|
||
|
return wrapper
|
||
|
|
||
|
|
||
|
def _unlocked(method, cache, key):
|
||
|
def wrapper(self, *args, **kwargs):
|
||
|
c = cache(self)
|
||
|
if c is None:
|
||
|
warn_cache_none()
|
||
|
return method(self, *args, **kwargs)
|
||
|
k = key(self, *args, **kwargs)
|
||
|
try:
|
||
|
return c[k]
|
||
|
except KeyError:
|
||
|
pass # key not found
|
||
|
v = method(self, *args, **kwargs)
|
||
|
try:
|
||
|
c[k] = v
|
||
|
except ValueError:
|
||
|
pass # value too large
|
||
|
return v
|
||
|
|
||
|
def cache_clear(self):
|
||
|
c = cache(self)
|
||
|
if c is not None:
|
||
|
c.clear()
|
||
|
|
||
|
wrapper.cache_clear = cache_clear
|
||
|
return wrapper
|
||
|
|
||
|
|
||
|
def _wrapper(method, cache, key, lock=None, cond=None):
|
||
|
if cond is not None and lock is not None:
|
||
|
wrapper = _condition(method, cache, key, lock, cond)
|
||
|
elif cond is not None:
|
||
|
wrapper = _condition(method, cache, key, cond, cond)
|
||
|
elif lock is not None:
|
||
|
wrapper = _locked(method, cache, key, lock)
|
||
|
else:
|
||
|
wrapper = _unlocked(method, cache, key)
|
||
|
|
||
|
wrapper.cache = cache
|
||
|
wrapper.cache_key = key
|
||
|
wrapper.cache_lock = lock if lock is not None else cond
|
||
|
wrapper.cache_condition = cond
|
||
|
|
||
|
return functools.update_wrapper(wrapper, method)
|