| OLD | NEW |
| (Empty) |
| 1 # Copyright 2015 The Chromium Authors. All rights reserved. | |
| 2 # Use of this source code is governed by a BSD-style license that can be | |
| 3 # found in the LICENSE file. | |
| 4 | |
| 5 """This module provides a decorator to cache the results of a function. | |
| 6 | |
| 7 Examples: | |
| 8 1. Decorate a function: | |
| 9 @cache_decorator.Cached() | |
| 10 def Test(a): | |
| 11 return a + a | |
| 12 | |
| 13 Test('a') | |
| 14 Test('a') # Returns the cached 'aa'. | |
| 15 | |
| 16 2. Decorate a method in a class: | |
| 17 class Downloader(object): | |
| 18 def __init__(self, url, retries): | |
| 19 self.url = url | |
| 20 self.retries = retries | |
| 21 | |
| 22 @property | |
| 23 def identifier(self): | |
| 24 return self.url | |
| 25 | |
| 26 @cache_decorator.Cached(): | |
| 27 def Download(self, path): | |
| 28 return urllib2.urlopen(self.url + '/' + path).read() | |
| 29 | |
| 30 d1 = Downloader('http://url', 4) | |
| 31 d1.Download('path') | |
| 32 | |
| 33 d2 = Downloader('http://url', 5) | |
| 34 d2.Download('path') # Returned the cached downloaded data. | |
| 35 """ | |
| 36 | |
| 37 import functools | |
| 38 import hashlib | |
| 39 import inspect | |
| 40 import logging | |
| 41 import pickle | |
| 42 | |
| 43 | |
| 44 def _DefaultKeyGenerator(func, args, kwargs): | |
| 45 """Generates a key from the function and arguments passed to it. | |
| 46 | |
| 47 Args: | |
| 48 func (function): An arbitrary function. | |
| 49 args (list): Positional arguments passed to ``func``. | |
| 50 kwargs (dict): Keyword arguments passed to ``func``. | |
| 51 | |
| 52 Returns: | |
| 53 A string to represent a call to the given function with the given arguments. | |
| 54 """ | |
| 55 params = inspect.getcallargs(func, *args, **kwargs) | |
| 56 for var_name in params: | |
| 57 if not hasattr(params[var_name], 'identifier'): | |
| 58 continue | |
| 59 | |
| 60 if callable(params[var_name].identifier): | |
| 61 params[var_name] = params[var_name].identifier() | |
| 62 else: | |
| 63 params[var_name] = params[var_name].identifier | |
| 64 | |
| 65 return hashlib.md5(pickle.dumps(params)).hexdigest() | |
| 66 | |
| 67 | |
| 68 def Cached(namespace=None, | |
| 69 expire_time=0, | |
| 70 key_generator=_DefaultKeyGenerator, | |
| 71 cache=None): | |
| 72 """Returns a decorator to cache the decorated function's results. | |
| 73 | |
| 74 However, if the function returns None, empty list/dict, empty string, or other | |
| 75 value that is evaluated as False, the results won't be cached. | |
| 76 | |
| 77 This decorator is to cache results of different calls to the decorated | |
| 78 function, and avoid executing it again if the calls are equivalent. Two calls | |
| 79 are equivalent, if the namespace is the same and the keys generated by the | |
| 80 ``key_generator`` are the same. | |
| 81 | |
| 82 The usage of this decorator requires that: | |
| 83 - If the default key generator is used, parameters passed to the decorated | |
| 84 function should be pickleable, or each of the parameter has an identifier | |
| 85 property or method which returns pickleable results. | |
| 86 - If the default cache is used, the returned results of the decorated | |
| 87 function should be pickleable. | |
| 88 | |
| 89 Args: | |
| 90 namespace (str): A prefix to the key for the cache. Default to the | |
| 91 combination of module name and function name of the decorated function. | |
| 92 expire_time (int): Expiration time, relative number of seconds from current | |
| 93 time (up to 0 month). Defaults to 0 -- never expire. | |
| 94 key_generator (function): A function to generate a key to represent a call | |
| 95 to the decorated function. Defaults to :func:`_DefaultKeyGenerator`. | |
| 96 cache (Cache): An instance of an implementation of interface `Cache`. | |
| 97 Defaults to None. | |
| 98 | |
| 99 Returns: | |
| 100 The cached results or the results of a new run of the decorated function. | |
| 101 """ | |
| 102 def GetPrefix(func, namespace): | |
| 103 return namespace or '%s.%s' % (func.__module__, func.__name__) | |
| 104 | |
| 105 def Decorator(func): | |
| 106 """Decorator to cache a function's results.""" | |
| 107 @functools.wraps(func) | |
| 108 def Wrapped(*args, **kwargs): | |
| 109 prefix = GetPrefix(func, namespace) | |
| 110 key = '%s-%s' % (prefix, key_generator(func, args, kwargs)) | |
| 111 | |
| 112 try: | |
| 113 result = cache.Get(key) | |
| 114 except Exception: # pragma: no cover. | |
| 115 result = None | |
| 116 logging.exception( | |
| 117 'Failed to get cached data for function %s.%s, args=%s, kwargs=%s', | |
| 118 func.__module__, func.__name__, repr(args), repr(kwargs)) | |
| 119 | |
| 120 if result is not None: | |
| 121 return result | |
| 122 | |
| 123 result = func(*args, **kwargs) | |
| 124 if result: | |
| 125 try: | |
| 126 cache.Set(key, result, expire_time=expire_time) | |
| 127 except Exception: # pragma: no cover. | |
| 128 logging.exception( | |
| 129 'Failed to cache data for function %s.%s, args=%s, kwargs=%s', | |
| 130 func.__module__, func.__name__, repr(args), repr(kwargs)) | |
| 131 | |
| 132 return result | |
| 133 | |
| 134 return Wrapped | |
| 135 | |
| 136 return Decorator | |
| OLD | NEW |