Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1337)

Unified Diff: appengine/findit/libs/cache_decorator.py

Issue 2644543006: [Culprit-Finder] Add generator cache decorator. (Closed)
Patch Set: Rebase and fix nits. Created 3 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: appengine/findit/libs/cache_decorator.py
diff --git a/appengine/findit/libs/cache_decorator.py b/appengine/findit/libs/cache_decorator.py
index d10fd15241f67d4fcd5b0730377286988778a958..99e03d46091d74e61929183fcc32c56e8c6d13e9 100644
--- a/appengine/findit/libs/cache_decorator.py
+++ b/appengine/findit/libs/cache_decorator.py
@@ -41,13 +41,18 @@ import inspect
import pickle
-def _DefaultKeyGenerator(func, args, kwargs):
+def _DefaultKeyGenerator(func, args, kwargs, namespace=None):
"""Generates a key from the function and arguments passed to it.
+ N.B. ``args`` and ``kwargs`` of function ``func`` should be pickleable,
+ or each of the parameter has an ``identifier`` property or method which
+ returns pickleable results.
+
Args:
func (function): An arbitrary function.
args (list): Positional arguments passed to ``func``.
kwargs (dict): Keyword arguments passed to ``func``.
+ namespace (str): A prefix to the key for the cache.
Returns:
A string to represent a call to the given function with the given arguments.
@@ -62,22 +67,14 @@ def _DefaultKeyGenerator(func, args, kwargs):
else:
params[var_name] = params[var_name].identifier
- return hashlib.md5(pickle.dumps(params)).hexdigest()
-
+ encoded_params = hashlib.md5(pickle.dumps(params)).hexdigest()
+ prefix = namespace or '%s.%s' % (func.__module__, func.__name__)
-def Cached(namespace=None,
- expire_time=0,
- key_generator=_DefaultKeyGenerator,
- cache=None):
- """Returns a decorator to cache the decorated function's results.
+ return '%s-%s' % (prefix, encoded_params)
- However, if the function returns None, empty list/dict, empty string, or other
- value that is evaluated as False, the results won't be cached.
- This decorator is to cache results of different calls to the decorated
- function, and avoid executing it again if the calls are equivalent. Two calls
- are equivalent, if the namespace is the same and the keys generated by the
- ``key_generator`` are the same.
+class CacheDecorator(object):
+ """Abstract decorator class to cache the decorated function.
The usage of this decorator requires that:
- If the default key generator is used, parameters passed to the decorated
@@ -85,52 +82,153 @@ def Cached(namespace=None,
property or method which returns pickleable results.
- If the default cache is used, the returned results of the decorated
function should be pickleable.
+ """
- Args:
- namespace (str): A prefix to the key for the cache. Default to the
+ def __init__(self, cache, namespace=None, expire_time=0,
+ key_generator=_DefaultKeyGenerator):
+ """
+ Args:
+ cache (Cache): An instance of an implementation of interface `Cache`.
+ Defaults to None.
+ namespace (str): A prefix to the key for the cache. Default to the
combination of module name and function name of the decorated function.
- expire_time (int): Expiration time, relative number of seconds from current
- time (up to 0 month). Defaults to 0 -- never expire.
- key_generator (function): A function to generate a key to represent a call
+ expire_time (int): Expiration time, relative number of seconds from
+ current time (up to 0 month). Defaults to 0 -- never expire.
+ key_generator (function): A function to generate a key to represent a call
to the decorated function. Defaults to :func:`_DefaultKeyGenerator`.
- cache (Cache): An instance of an implementation of interface `Cache`.
- Defaults to None.
+ """
+ self._cache = cache
+ self._namespace = namespace
+ self._expire_time = expire_time
+ self._key_generator = key_generator
+
+ def GetCache(self, key, func, args, kwargs):
+ """Gets cached result for key.
+
+ Args:
+ key (str): The key to retriev result from.
+ func (callable): The function to decorate.
+ args (iterable): The argument list of the decorated function.
+ args (dict): The keyword arguments of the decorated function.
+
+ Returns:
+ Returns cached result of key from the ``Cache`` instance.
+ N.B. If there is Exception retrieving the cached result, the returned
+ result will be set to None.
+ """
+ try:
+ result = self._cache.Get(key)
+ except Exception: # pragma: no cover.
+ result = None
+ logging.exception(
+ 'Failed to get cached data for function %s.%s, args=%s, kwargs=%s',
+ func.__module__, func.__name__, repr(args), repr(kwargs))
+
+ return result
+
+ def SetCache(self, key, result, func, args, kwargs):
+ """Sets result to ``self._cache``.
+
+ Args:
+ key (str): The key to retriev result from.
+ result (any type): The result of the key.
+ func (callable): The function to decorate.
+ args (iterable): The argument list of the decorated function.
+ args (dict): The keyword arguments of the decorated function.
+
+ Returns:
+ Boolean indicating if ``result`` was successfully set or not.
+ """
+ try:
+ self._cache.Set(key, result, expire_time=self._expire_time)
+ except Exception: # pragma: no cover.
+ logging.exception(
+ 'Failed to cache data for function %s.%s, args=%s, kwargs=%s',
+ func.__module__, func.__name__, repr(args), repr(kwargs))
+ return False
+
+ return True
+
+ def __call__(self, func):
+ """Returns a wrapped function of ``func`` which utilize ``self._cache``."""
+ raise NotImplementedError()
+
+
+class Cached(CacheDecorator):
+ """Decorator to cache function's results.
+
+ N.B. the decorated function should have return values, because if the function
+ returns None, empty list/dict, empty string, or other value that is evaluated
+ as False, the results won't be cached.
- Returns:
- The cached results or the results of a new run of the decorated function.
+ This decorator is to cache results of different calls to the decorated
+ function, and avoid executing it again if the calls are equivalent. Two calls
+ are equivalent, if the namespace is the same and the keys generated by the
+ ``key_generator`` are the same.
"""
- def GetPrefix(func, namespace):
- return namespace or '%s.%s' % (func.__module__, func.__name__)
- def Decorator(func):
+ def __call__(self, func):
"""Decorator to cache a function's results."""
@functools.wraps(func)
def Wrapped(*args, **kwargs):
- prefix = GetPrefix(func, namespace)
- key = '%s-%s' % (prefix, key_generator(func, args, kwargs))
-
- try:
- result = cache.Get(key)
- except Exception: # pragma: no cover.
- result = None
- logging.exception(
- 'Failed to get cached data for function %s.%s, args=%s, kwargs=%s',
- func.__module__, func.__name__, repr(args), repr(kwargs))
+ key = self._key_generator(func, args, kwargs, namespace=self._namespace)
+ cached_result = self.GetCache(key, func, args, kwargs)
- if result is not None:
- return result
+ if cached_result is not None:
+ return cached_result
result = func(*args, **kwargs)
if result:
- try:
- cache.Set(key, result, expire_time=expire_time)
- except Exception: # pragma: no cover.
- logging.exception(
- 'Failed to cache data for function %s.%s, args=%s, kwargs=%s',
- func.__module__, func.__name__, repr(args), repr(kwargs))
+ self.SetCache(key, result, func, args, kwargs)
return result
return Wrapped
- return Decorator
+
+class GeneratorCached(CacheDecorator):
+ """Decorator to cache a generator function.
+
+ N.B. the decorated function must be a generator which ``yield``s results.
+
+ The key of the generator function will map to a list of sub-keys mapping to
+ each element result.N.B. All the results must be cached, no matter it's
+ empty or not. If any result failed to be cached, the whole caching is
+ considered failed and the key to the generator must NOT be set.
+
+ This decorator is to cache all results of the generator, and ``yield`` them
+ one by one from ``self._cache``. Namely, a generator is cached only after it
+ was exhausted in a function call. (e.g. a for loop without break statement.)
+ """
+
+ def __call__(self, func):
+ """Decorator to cache a generator function."""
+ @functools.wraps(func)
+ def Wrapped(*args, **kwargs):
+ key = self._key_generator(func, args, kwargs, namespace=self._namespace)
+ cached_keys = self.GetCache(key, func, args, kwargs)
+
+ if cached_keys is not None:
+ for cached_key in cached_keys:
+ yield self.GetCache(cached_key, func, args, kwargs)
+
+ result_iter = func(*args, **kwargs)
+ result_keys = []
+
+ cache_success = True
+ for index, result in enumerate(result_iter):
+ yield result
+ if not cache_success:
+ continue
+
+ result_key = '%s-%d' % (key, index)
+ if not self.SetCache(result_key, result, func, args, kwargs):
+ cache_success = False
+ continue
+
+ result_keys.append(result_key)
+
+ if cache_success:
+ self.SetCache(key, result_keys, func, args, kwargs)
+
+ return Wrapped
« no previous file with comments | « appengine/findit/gae_libs/gitiles/cached_gitiles_repository.py ('k') | appengine/findit/libs/test/cache_decorator_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698