diff options
Diffstat (limited to 'src/leap/common')
-rw-r--r-- | src/leap/common/decorators.py | 92 |
1 files changed, 66 insertions, 26 deletions
diff --git a/src/leap/common/decorators.py b/src/leap/common/decorators.py index 99c3653..4b07ea9 100644 --- a/src/leap/common/decorators.py +++ b/src/leap/common/decorators.py @@ -22,6 +22,13 @@ import datetime import functools import logging +try: + from twisted.internet import defer +except ImportError: + class defer: + class Deferred: + pass + logger = logging.getLogger(__name__) @@ -59,6 +66,7 @@ class _memoized(object): # consume a huge amount of memory. self.cache = {} self.cache_ts = {} + self.is_deferred = {} def __call__(self, *args, **kwargs): """ @@ -67,28 +75,7 @@ class _memoized(object): :tyoe args: tuple :type kwargs: dict """ - def ret_or_raise(value): - """ - Returns the value except if it is an exception, - in which case it's raised. - """ - if isinstance(value, Exception): - raise value - return value - - if self.is_method: - # forget about `self` as key - key_args = args[1:] - else: - key_args = args - - if self.ignore_kwargs is True: - key = key_args - else: - key = (key_args, frozenset( - [(k, v) for k, v in kwargs.items() - if k not in self.ignore_kwargs])) - + key = self._build_key(*args, **kwargs) if not isinstance(key, collections.Hashable): # uncacheable. a list, for instance. # better to not cache than blow up. @@ -97,9 +84,9 @@ class _memoized(object): if key in self.cache: if self._is_cache_still_valid(key): - value = self.cache[key] logger.debug("Got value from cache...") - return ret_or_raise(value) + value = self._get_value(key) + return self._ret_or_raise(value) else: logger.debug("Cache is invalid, evaluating again...") @@ -109,9 +96,52 @@ class _memoized(object): except Exception as exc: logger.error("Exception while calling function: %r" % (exc,)) value = exc - self.cache[key] = value + + if isinstance(value, defer.Deferred): + value.addBoth(self._store_deferred_value(key)) + else: + self.cache[key] = value + self.is_deferred[key] = False self.cache_ts[key] = datetime.datetime.now() - return ret_or_raise(value) + return self._ret_or_raise(value) + + def _build_key(self, *args, **kwargs): + """ + Build key from the function arguments + """ + if self.is_method: + # forget about `self` as key + key_args = args[1:] + else: + key_args = args + + if self.ignore_kwargs is True: + key = key_args + else: + key = (key_args, frozenset( + [(k, v) for k, v in kwargs.items() + if k not in self.ignore_kwargs])) + return key + + def _get_value(self, key): + """ + Get a value form cache for a key + """ + if self.is_deferred[key]: + value = self.cache[key] + # it produces an errback if value is Failure + return defer.succeed(value) + else: + return self.cache[key] + + def _ret_or_raise(self, value): + """ + Returns the value except if it is an exception, + in which case it's raised. + """ + if isinstance(value, Exception): + raise value + return value def _is_cache_still_valid(self, key, now=datetime.datetime.now): """ @@ -131,6 +161,16 @@ class _memoized(object): delta = datetime.timedelta(seconds=self.CACHE_INVALIDATION_DELTA) return (now() - cached_ts) < delta + def _store_deferred_value(self, key): + """ + Returns a callback to store values from deferreds + """ + def callback(value): + self.cache[key] = value + self.is_deferred[key] = True + return value + return callback + def __repr__(self): """ Return the function's docstring. |