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

Side by Side Diff: appengine/findit/libs/cache_decorator.py

Issue 2644543006: [Culprit-Finder] Add generator cache decorator. (Closed)
Patch Set: Rebase. 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 unified diff | Download patch
OLDNEW
1 # Copyright 2015 The Chromium Authors. All rights reserved. 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 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 """This module provides a decorator to cache the results of a function. 5 """This module provides a decorator to cache the results of a function.
6 6
7 Examples: 7 Examples:
8 1. Decorate a function: 8 1. Decorate a function:
9 @cache_decorator.Cached() 9 @cache_decorator.Cached()
10 def Test(a): 10 def Test(a):
(...skipping 23 matching lines...) Expand all
34 d2.Download('path') # Returned the cached downloaded data. 34 d2.Download('path') # Returned the cached downloaded data.
35 """ 35 """
36 36
37 import functools 37 import functools
38 import hashlib 38 import hashlib
39 import logging 39 import logging
40 import inspect 40 import inspect
41 import pickle 41 import pickle
42 42
43 43
44 def _DefaultKeyGenerator(func, args, kwargs): 44 def _DefaultKeyGenerator(func, args, kwargs, namespace=None):
45 """Generates a key from the function and arguments passed to it. 45 """Generates a key from the function and arguments passed to it.
46 46
47 N.B. ``args`` and ``kwargs`` of function ``func`` should be pickleable,
48 or each of the parameter has an ``identifier`` property or method which
49 returns pickleable results.
50
47 Args: 51 Args:
48 func (function): An arbitrary function. 52 func (function): An arbitrary function.
49 args (list): Positional arguments passed to ``func``. 53 args (list): Positional arguments passed to ``func``.
50 kwargs (dict): Keyword arguments passed to ``func``. 54 kwargs (dict): Keyword arguments passed to ``func``.
55 namespace (str): A prefix to the key for the cache.
51 56
52 Returns: 57 Returns:
53 A string to represent a call to the given function with the given arguments. 58 A string to represent a call to the given function with the given arguments.
54 """ 59 """
55 params = inspect.getcallargs(func, *args, **kwargs) 60 params = inspect.getcallargs(func, *args, **kwargs)
56 for var_name in params: 61 for var_name in params:
57 if not hasattr(params[var_name], 'identifier'): 62 if not hasattr(params[var_name], 'identifier'):
58 continue 63 continue
59 64
60 if callable(params[var_name].identifier): 65 if callable(params[var_name].identifier):
61 params[var_name] = params[var_name].identifier() 66 params[var_name] = params[var_name].identifier()
62 else: 67 else:
63 params[var_name] = params[var_name].identifier 68 params[var_name] = params[var_name].identifier
64 69
65 return hashlib.md5(pickle.dumps(params)).hexdigest() 70 encoded_params = hashlib.md5(pickle.dumps(params)).hexdigest()
71 prefix = namespace or '%s.%s' % (func.__module__, func.__name__)
72
73 return '%s-%s' % (prefix, encoded_params)
66 74
67 75
68 def Cached(namespace=None, 76 class CacheDecorator(object):
69 expire_time=0, 77 """Abstract decorator class to cache the decorated function.
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 78
82 The usage of this decorator requires that: 79 The usage of this decorator requires that:
83 - If the default key generator is used, parameters passed to the decorated 80 - 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 81 function should be pickleable, or each of the parameter has an identifier
85 property or method which returns pickleable results. 82 property or method which returns pickleable results.
86 - If the default cache is used, the returned results of the decorated 83 - If the default cache is used, the returned results of the decorated
87 function should be pickleable. 84 function should be pickleable.
85 """
88 86
89 Args: 87 def __init__(self, cache, namespace=None, expire_time=0,
90 namespace (str): A prefix to the key for the cache. Default to the 88 key_generator=_DefaultKeyGenerator):
89 """
90 Args:
91 cache (Cache): An instance of an implementation of interface `Cache`.
92 Defaults to None.
93 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. 94 combination of module name and function name of the decorated function.
92 expire_time (int): Expiration time, relative number of seconds from current 95 expire_time (int): Expiration time, relative number of seconds from
93 time (up to 0 month). Defaults to 0 -- never expire. 96 current time (up to 0 month). Defaults to 0 -- never expire.
94 key_generator (function): A function to generate a key to represent a call 97 key_generator (function): A function to generate a key to represent a call
95 to the decorated function. Defaults to :func:`_DefaultKeyGenerator`. 98 to the decorated function. Defaults to :func:`_DefaultKeyGenerator`.
96 cache (Cache): An instance of an implementation of interface `Cache`. 99 """
97 Defaults to None. 100 self._cache = cache
101 self._namespace = namespace
102 self._expire_time = expire_time
103 self._key_generator = key_generator
98 104
99 Returns: 105 def GetCache(self, key, func, args, kwargs):
100 The cached results or the results of a new run of the decorated function. 106 """Gets cached result for key.
107
108 Args:
109 key (str): The key to retriev result from.
110 func (callable): The function to decorate.
111 args (iterable): The argument list of the decorated function.
112 args (dict): The keyword arguments of the decorated function.
chanli 2017/01/21 07:11:56 Nit: kwargs
113
114 Returns:
115 Returns cached result of key from the ``Cache`` instance.
116 N.B. If there is Exception retrieving the cached result, the returned
117 result will be set to None.
118 """
119 try:
120 result = self._cache.Get(key)
121 except Exception: # pragma: no cover.
122 result = None
123 logging.exception(
124 'Failed to get cached data for function %s.%s, args=%s, kwargs=%s',
125 func.__module__, func.__name__, repr(args), repr(kwargs))
126
127 return result
128
129 def SetCache(self, key, result, func, args, kwargs):
130 """Sets result to ``self._cache``.
131
132 Args:
133 key (str): The key to retriev result from.
134 result (any type): The result of the key.
135 func (callable): The function to decorate.
136 args (iterable): The argument list of the decorated function.
137 args (dict): The keyword arguments of the decorated function.
chanli 2017/01/21 07:11:56 Nit: kwargs
138
139 Returns:
140 Boolean indicating if ``result`` was successfully set or not.
141 """
142 try:
143 self._cache.Set(key, result, expire_time=self._expire_time)
144 except Exception: # pragma: no cover.
145 logging.exception(
146 'Failed to cache data for function %s.%s, args=%s, kwargs=%s',
147 func.__module__, func.__name__, repr(args), repr(kwargs))
148 return False
149
150 return True
151
152 def __call__(self, func):
153 """Returns a wrapped function of ``func`` which utilize ``self._cache``."""
154 raise NotImplementedError()
155
156
157 class Cached(CacheDecorator):
158 """Decorator to cache function's results.
159
160 N.B. the decorated function should have return values, because if the function
161 returns None, empty list/dict, empty string, or other value that is evaluated
162 as False, the results won't be cached.
163
164 This decorator is to cache results of different calls to the decorated
165 function, and avoid executing it again if the calls are equivalent. Two calls
166 are equivalent, if the namespace is the same and the keys generated by the
167 ``key_generator`` are the same.
101 """ 168 """
102 def GetPrefix(func, namespace):
103 return namespace or '%s.%s' % (func.__module__, func.__name__)
104 169
105 def Decorator(func): 170 def __call__(self, func):
106 """Decorator to cache a function's results.""" 171 """Decorator to cache a function's results."""
107 @functools.wraps(func) 172 @functools.wraps(func)
108 def Wrapped(*args, **kwargs): 173 def Wrapped(*args, **kwargs):
109 prefix = GetPrefix(func, namespace) 174 key = self._key_generator(func, args, kwargs, namespace=self._namespace)
110 key = '%s-%s' % (prefix, key_generator(func, args, kwargs)) 175 cached_result = self.GetCache(key, func, args, kwargs)
111 176
112 try: 177 if cached_result is not None:
113 result = cache.Get(key) 178 return cached_result
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 179
123 result = func(*args, **kwargs) 180 result = func(*args, **kwargs)
124 if result: 181 if result:
125 try: 182 self.SetCache(key, result, func, args, kwargs)
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 183
132 return result 184 return result
133 185
134 return Wrapped 186 return Wrapped
135 187
136 return Decorator 188
189 class GeneratorCached(CacheDecorator):
190 """Decorator to cache a generator function.
191
192 N.B. the decorated function must be a generator which ``yield``s results.
193
194 The key of the generator function will map to a list of sub-keys mapping to
195 each element result.N.B. All the results must be cached, no matter it's
196 empty or not. If any result failed to be cached, the whole caching is
197 considered failed and the key to the generator must NOT be set.
198
199 This decorator is to cache all results of the generator, and ``yield`` them
200 one by one from ``self._cache``.
201 """
202
203 def __call__(self, func):
204 """Decorator to cache a generator function."""
205 @functools.wraps(func)
206 def Wrapped(*args, **kwargs):
207 key = self._key_generator(func, args, kwargs, namespace=self._namespace)
208 cached_keys = self.GetCache(key, func, args, kwargs)
209
210 if cached_keys is not None:
211 for cached_key in cached_keys:
212 yield self.GetCache(cached_key, func, args, kwargs)
213
214 result_iter = func(*args, **kwargs)
215 result_keys = []
216
217 cache_success = True
218 for index, result in enumerate(result_iter):
219 yield result
220 result_key = '%s-%d' % (key, index)
221 if not cache_success or not self.SetCache(result_key, result,
222 func, args, kwargs):
223 cache_success = False
224 continue
225
226 result_keys.append(result_key)
227
228 if cache_success:
229 self.SetCache(key, result_keys, func, args, kwargs)
230
231 return Wrapped
OLDNEW
« 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