Chromium Code Reviews| 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..5ca50f1b3a0beefbcd4210292e71d3dd7204dd39 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,150 @@ 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. |
|
chanli
2017/01/21 07:11:56
Nit: kwargs
|
| + |
| + 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. |
|
chanli
2017/01/21 07:11:56
Nit: kwargs
|
| + |
| + 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``. |
| + """ |
| + |
| + 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 |
| + result_key = '%s-%d' % (key, index) |
| + if not cache_success or 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 |