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

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

Issue 2557553002: [Culprit-Finder] Seperate gae related part in cache_decorator and gitile repository to gae_libs/ (Closed)
Patch Set: Fix nits. Created 4 years 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
« no previous file with comments | « appengine/findit/lib/cache.py ('k') | appengine/findit/lib/gitiles/gitiles_repository.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 # TODO(http://crbug.com/660466): We should try to break dependencies.
6
7 """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.
8 6
9 Examples: 7 Examples:
10 1. Decorate a function: 8 1. Decorate a function:
11 @cache_decorator.Cached() 9 @cache_decorator.Cached()
12 def Test(a): 10 def Test(a):
13 return a + a 11 return a + a
14 12
15 Test('a') 13 Test('a')
16 Test('a') # Returns the cached 'aa'. 14 Test('a') # Returns the cached 'aa'.
(...skipping 12 matching lines...) Expand all
29 def Download(self, path): 27 def Download(self, path):
30 return urllib2.urlopen(self.url + '/' + path).read() 28 return urllib2.urlopen(self.url + '/' + path).read()
31 29
32 d1 = Downloader('http://url', 4) 30 d1 = Downloader('http://url', 4)
33 d1.Download('path') 31 d1.Download('path')
34 32
35 d2 = Downloader('http://url', 5) 33 d2 = Downloader('http://url', 5)
36 d2.Download('path') # Returned the cached downloaded data. 34 d2.Download('path') # Returned the cached downloaded data.
37 """ 35 """
38 36
39 import cStringIO
40 import functools 37 import functools
41 import hashlib 38 import hashlib
42 import inspect 39 import inspect
43 import logging 40 import logging
44 import pickle 41 import pickle
45 import zlib
46
47 from google.appengine.api import memcache
48
49
50 # TODO(katesonia): Change this to a better name, e.g. Cache.
51 class Cacher(object):
52 """An interface to cache and retrieve data.
53
54 Subclasses should implement the Get/Set functions.
55 TODO: Add a Delete function (default to no-op) if needed later.
56 """
57 def Get(self, key):
58 """Returns the cached data for the given key if available.
59
60 Args:
61 key (str): The key to identify the cached data.
62 """
63 raise NotImplementedError()
64
65 def Set(self, key, data, expire_time=0):
66 """Cache the given data which is identified by the given key.
67
68 Args:
69 key (str): The key to identify the cached data.
70 data (object): The python object to be cached.
71 expire_time (int): Number of seconds from current time (up to 1 month).
72 """
73 raise NotImplementedError()
74
75
76 class PickledMemCacher(Cacher):
77 """A memcache-backed implementation of the interface Cacher.
78
79 The data to be cached should be pickleable.
80 Limitation: size of the pickled data and key should be <= 1MB.
81 """
82 def Get(self, key):
83 return memcache.get(key)
84
85 def Set(self, key, data, expire_time=0):
86 return memcache.set(key, data, time=expire_time)
87
88
89 class _CachedItemMetaData(object):
90 def __init__(self, number):
91 self.number = number
92
93
94 class CompressedMemCacher(Cacher):
95 """A memcache-backed implementation of the interface Cacher with compression.
96
97 The data to be cached would be pickled and then compressed.
98 Data still > 1MB will be split into sub-piece and stored separately.
99 During retrieval, if any sub-piece is missing, None is returned.
100 """
101 CHUNK_SIZE = 990000
102
103 def Get(self, key):
104 data = memcache.get(key)
105 if isinstance(data, _CachedItemMetaData):
106 num = data.number
107 sub_keys = ['%s-%s' % (key, i) for i in range(num)]
108 all_data = memcache.get_multi(sub_keys)
109 if len(all_data) != num: # Some data is missing.
110 return None
111
112 data_output = cStringIO.StringIO()
113 for sub_key in sub_keys:
114 data_output.write(all_data[sub_key])
115 data = data_output.getvalue()
116
117 return None if data is None else pickle.loads(zlib.decompress(data))
118
119 def Set(self, key, data, expire_time=0):
120 pickled_data = pickle.dumps(data)
121 compressed_data = zlib.compress(pickled_data)
122
123 all_data = {}
124 if len(compressed_data) > self.CHUNK_SIZE:
125 num = 0
126 for index in range(0, len(compressed_data), self.CHUNK_SIZE):
127 sub_key = '%s-%s' % (key, num)
128 all_data[sub_key] = compressed_data[index : index + self.CHUNK_SIZE]
129 num += 1
130
131 all_data[key] = _CachedItemMetaData(num)
132 else:
133 all_data[key] = compressed_data
134
135 keys_not_set = memcache.set_multi(all_data, time=expire_time)
136 return len(keys_not_set) == 0
137 42
138 43
139 def _DefaultKeyGenerator(func, args, kwargs): 44 def _DefaultKeyGenerator(func, args, kwargs):
140 """Generates a key from the function and arguments passed to it. 45 """Generates a key from the function and arguments passed to it.
141 46
142 Args: 47 Args:
143 func (function): An arbitrary function. 48 func (function): An arbitrary function.
144 args (list): Positional arguments passed to ``func``. 49 args (list): Positional arguments passed to ``func``.
145 kwargs (dict): Keyword arguments passed to ``func``. 50 kwargs (dict): Keyword arguments passed to ``func``.
146 51
147 Returns: 52 Returns:
148 A string to represent a call to the given function with the given arguments. 53 A string to represent a call to the given function with the given arguments.
149 """ 54 """
150 params = inspect.getcallargs(func, *args, **kwargs) 55 params = inspect.getcallargs(func, *args, **kwargs)
151 for var_name in params: 56 for var_name in params:
152 if not hasattr(params[var_name], 'identifier'): 57 if not hasattr(params[var_name], 'identifier'):
153 continue 58 continue
154 59
155 if callable(params[var_name].identifier): 60 if callable(params[var_name].identifier):
156 params[var_name] = params[var_name].identifier() 61 params[var_name] = params[var_name].identifier()
157 else: 62 else:
158 params[var_name] = params[var_name].identifier 63 params[var_name] = params[var_name].identifier
159 64
160 return hashlib.md5(pickle.dumps(params)).hexdigest() 65 return hashlib.md5(pickle.dumps(params)).hexdigest()
161 66
162 67
163 def Cached(namespace=None, 68 def Cached(namespace=None,
164 expire_time=0, 69 expire_time=0,
165 key_generator=_DefaultKeyGenerator, 70 key_generator=_DefaultKeyGenerator,
166 cacher=PickledMemCacher()): 71 cache=None):
167 """Returns a decorator to cache the decorated function's results. 72 """Returns a decorator to cache the decorated function's results.
168 73
169 However, if the function returns None, empty list/dict, empty string, or other 74 However, if the function returns None, empty list/dict, empty string, or other
170 value that is evaluated as False, the results won't be cached. 75 value that is evaluated as False, the results won't be cached.
171 76
172 This decorator is to cache results of different calls to the decorated 77 This decorator is to cache results of different calls to the decorated
173 function, and avoid executing it again if the calls are equivalent. Two calls 78 function, and avoid executing it again if the calls are equivalent. Two calls
174 are equivalent, if the namespace is the same and the keys generated by the 79 are equivalent, if the namespace is the same and the keys generated by the
175 ``key_generator`` are the same. 80 ``key_generator`` are the same.
176 81
177 The usage of this decorator requires that: 82 The usage of this decorator requires that:
178 - If the default key generator is used, parameters passed to the decorated 83 - If the default key generator is used, parameters passed to the decorated
179 function should be pickleable, or each of the parameter has an identifier 84 function should be pickleable, or each of the parameter has an identifier
180 property or method which returns pickleable results. 85 property or method which returns pickleable results.
181 - If the default cacher is used, the returned results of the decorated 86 - If the default cache is used, the returned results of the decorated
182 function should be pickleable. 87 function should be pickleable.
183 88
184 Args: 89 Args:
185 namespace (str): A prefix to the key for the cache. Default to the 90 namespace (str): A prefix to the key for the cache. Default to the
186 combination of module name and function name of the decorated function. 91 combination of module name and function name of the decorated function.
187 expire_time (int): Expiration time, relative number of seconds from current 92 expire_time (int): Expiration time, relative number of seconds from current
188 time (up to 1 month). Defaults to 0 -- never expire. 93 time (up to 0 month). Defaults to 0 -- never expire.
189 key_generator (function): A function to generate a key to represent a call 94 key_generator (function): A function to generate a key to represent a call
190 to the decorated function. Defaults to :func:`_DefaultKeyGenerator`. 95 to the decorated function. Defaults to :func:`_DefaultKeyGenerator`.
191 cacher (Cacher): An instance of an implementation of interface `Cacher`. 96 cache (Cache): An instance of an implementation of interface `Cache`.
192 Defaults to one of `PickledMemCacher` which is based on memcache. 97 Defaults to None.
193 98
194 Returns: 99 Returns:
195 The cached results or the results of a new run of the decorated function. 100 The cached results or the results of a new run of the decorated function.
196 """ 101 """
197 def GetPrefix(func, namespace): 102 def GetPrefix(func, namespace):
198 return namespace or '%s.%s' % (func.__module__, func.__name__) 103 return namespace or '%s.%s' % (func.__module__, func.__name__)
199 104
200 def Decorator(func): 105 def Decorator(func):
201 """Decorator to cache a function's results.""" 106 """Decorator to cache a function's results."""
202 @functools.wraps(func) 107 @functools.wraps(func)
203 def Wrapped(*args, **kwargs): 108 def Wrapped(*args, **kwargs):
204 prefix = GetPrefix(func, namespace) 109 prefix = GetPrefix(func, namespace)
205 key = '%s-%s' % (prefix, key_generator(func, args, kwargs)) 110 key = '%s-%s' % (prefix, key_generator(func, args, kwargs))
206 111
207 try: 112 try:
208 result = cacher.Get(key) 113 result = cache.Get(key)
209 except Exception: # pragma: no cover. 114 except Exception: # pragma: no cover.
210 result = None 115 result = None
211 logging.exception( 116 logging.exception(
212 'Failed to get cached data for function %s.%s, args=%s, kwargs=%s', 117 'Failed to get cached data for function %s.%s, args=%s, kwargs=%s',
213 func.__module__, func.__name__, repr(args), repr(kwargs)) 118 func.__module__, func.__name__, repr(args), repr(kwargs))
214 119
215 if result is not None: 120 if result is not None:
216 return result 121 return result
217 122
218 result = func(*args, **kwargs) 123 result = func(*args, **kwargs)
219 if result: 124 if result:
220 try: 125 try:
221 cacher.Set(key, result, expire_time=expire_time) 126 cache.Set(key, result, expire_time=expire_time)
222 except Exception: # pragma: no cover. 127 except Exception: # pragma: no cover.
223 logging.exception( 128 logging.exception(
224 'Failed to cache data for function %s.%s, args=%s, kwargs=%s', 129 'Failed to cache data for function %s.%s, args=%s, kwargs=%s',
225 func.__module__, func.__name__, repr(args), repr(kwargs)) 130 func.__module__, func.__name__, repr(args), repr(kwargs))
226 131
227 return result 132 return result
228 133
229 return Wrapped 134 return Wrapped
230 135
231 return Decorator 136 return Decorator
OLDNEW
« no previous file with comments | « appengine/findit/lib/cache.py ('k') | appengine/findit/lib/gitiles/gitiles_repository.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698