diff options
author | Tomás Touceda <chiiph@leap.se> | 2013-12-19 14:35:43 -0300 |
---|---|---|
committer | Tomás Touceda <chiiph@leap.se> | 2013-12-19 14:35:43 -0300 |
commit | 646dd2fd5fb3980f08490dcba03948164ab62bdf (patch) | |
tree | d56acf0ae85b37fff66cdf417dea1e4fd6f371c1 /src/leap/common/decorators.py | |
parent | f498b0eca553176f69f7be4eb42206f579b9be8b (diff) | |
parent | d46207491b9c0c0fc3a79256fe00bfe3f60712a0 (diff) |
Merge remote-tracking branch 'refs/remotes/kali/bug/fix-memoize' into develop
Diffstat (limited to 'src/leap/common/decorators.py')
-rw-r--r-- | src/leap/common/decorators.py | 69 |
1 files changed, 58 insertions, 11 deletions
diff --git a/src/leap/common/decorators.py b/src/leap/common/decorators.py index ec6171a..2ef6711 100644 --- a/src/leap/common/decorators.py +++ b/src/leap/common/decorators.py @@ -18,6 +18,7 @@ Useful decorators. """ import collections +import datetime import functools import logging @@ -32,7 +33,13 @@ class _memoized(object): If called later with the same arguments, the cached value is returned (not reevaluated). """ - def __init__(self, func, ignore_kwargs=None, is_method=False): + + # cache invalidation time, in seconds + CACHE_INVALIDATION_DELTA = 1800 + + def __init__(self, func, ignore_kwargs=None, is_method=False, + invalidation=None): + """ :param ignore_kwargs: If True, ignore all kwargs. If tuple, ignore those kwargs. @@ -45,9 +52,13 @@ class _memoized(object): self.is_method = is_method self.func = func + if invalidation: + self.CACHE_INVALIDATION_DELTA = invalidation + # TODO should put bounds to the cache dict so we do not # consume a huge amount of memory. self.cache = {} + self.cache_ts = {} def __call__(self, *args, **kwargs): """ @@ -56,9 +67,21 @@ 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: @@ -73,16 +96,40 @@ class _memoized(object): return self.func(*args, **kwargs) if key in self.cache: - logger.debug("Got value from cache...") - return self.cache[key] - else: - try: - value = self.func(*args, **kwargs) - except Exception as exc: - logger.error("Exception while calling function: %r" % (exc,)) - value = None - self.cache[key] = value - return value + if self._is_cache_still_valid(key): + value = self.cache[key] + logger.debug("Got value from cache...") + return ret_or_raise(value) + else: + logger.debug("Cache is invalid, evaluating again...") + + # no cache, or cache invalid + try: + value = self.func(*args, **kwargs) + except Exception as exc: + logger.error("Exception while calling function: %r" % (exc,)) + value = exc + self.cache[key] = value + self.cache_ts[key] = datetime.datetime.now() + return ret_or_raise(value) + + def _is_cache_still_valid(self, key, now=datetime.datetime.now): + """ + Returns True if the cache value is still valid, False otherwise. + + For now, this happen if less than CACHE_INVALIDATION_DELTA seconds + have passed from the time in which we recorded the cached value. + + :param key: the key to lookup in the cache + :type key: hashable + :param now: a callable that returns a datetime object. override + for dependency injection during testing. + :type now: callable + :rtype: bool + """ + cached_ts = self.cache_ts[key] + delta = datetime.timedelta(seconds=self.CACHE_INVALIDATION_DELTA) + return (now() - cached_ts) < delta def __repr__(self): """ |