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

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

Issue 2344443005: [Findit] Factoring the gitiles (etc) stuff out into its own directory (Closed)
Patch Set: reordering imports Created 4 years, 2 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
« no previous file with comments | « appengine/findit/common/blame.py ('k') | appengine/findit/common/change_log.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: appengine/findit/common/cache_decorator.py
diff --git a/appengine/findit/common/cache_decorator.py b/appengine/findit/common/cache_decorator.py
deleted file mode 100644
index 6f1bb1efe42dc866aa7841e4df8eb84db698f26f..0000000000000000000000000000000000000000
--- a/appengine/findit/common/cache_decorator.py
+++ /dev/null
@@ -1,228 +0,0 @@
-# Copyright 2015 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""This module provides a decorator to cache the results of a function.
-
- Examples:
- 1. Decorate a function:
- @cache_decorator.Cached()
- def Test(a):
- return a + a
-
- Test('a')
- Test('a') # Returns the cached 'aa'.
-
- 2. Decorate a method in a class:
- class Downloader(object):
- def __init__(self, url, retries):
- self.url = url
- self.retries = retries
-
- @property
- def identifier(self):
- return self.url
-
- @cache_decorator.Cached():
- def Download(self, path):
- return urllib2.urlopen(self.url + '/' + path).read()
-
- d1 = Downloader('http://url', 4)
- d1.Download('path')
-
- d2 = Downloader('http://url', 5)
- d2.Download('path') # Returned the cached downloaded data.
-"""
-
-import cStringIO
-import functools
-import hashlib
-import inspect
-import logging
-import pickle
-import zlib
-
-from google.appengine.api import memcache
-
-
-class Cacher(object):
- """An interface to cache and retrieve data.
-
- Subclasses should implement the Get/Set functions.
- TODO: Add a Delete function (default to no-op) if needed later.
- """
- def Get(self, key):
- """Returns the cached data for the given key if available.
-
- Args:
- key (str): The key to identify the cached data.
- """
- raise NotImplementedError()
-
- def Set(self, key, data, expire_time=0):
- """Cache the given data which is identified by the given key.
-
- Args:
- key (str): The key to identify the cached data.
- data (object): The python object to be cached.
- expire_time (int): Number of seconds from current time (up to 1 month).
- """
- raise NotImplementedError()
-
-
-class PickledMemCacher(Cacher):
- """A memcache-backed implementation of the interface Cacher.
-
- The data to be cached should be pickleable.
- Limitation: size of the pickled data and key should be <= 1MB.
- """
- def Get(self, key):
- return memcache.get(key)
-
- def Set(self, key, data, expire_time=0):
- return memcache.set(key, data, time=expire_time)
-
-
-class _CachedItemMetaData(object):
- def __init__(self, number):
- self.number = number
-
-
-class CompressedMemCacher(Cacher):
- """A memcache-backed implementation of the interface Cacher with compression.
-
- The data to be cached would be pickled and then compressed.
- Data still > 1MB will be split into sub-piece and stored separately.
- During retrieval, if any sub-piece is missing, None is returned.
- """
- CHUNK_SIZE = 990000
-
- def Get(self, key):
- data = memcache.get(key)
- if isinstance(data, _CachedItemMetaData):
- num = data.number
- sub_keys = ['%s-%s' % (key, i) for i in range(num)]
- all_data = memcache.get_multi(sub_keys)
- if len(all_data) != num: # Some data is missing.
- return None
-
- data_output = cStringIO.StringIO()
- for sub_key in sub_keys:
- data_output.write(all_data[sub_key])
- data = data_output.getvalue()
-
- return None if data is None else pickle.loads(zlib.decompress(data))
-
- def Set(self, key, data, expire_time=0):
- pickled_data = pickle.dumps(data)
- compressed_data = zlib.compress(pickled_data)
-
- all_data = {}
- if len(compressed_data) > self.CHUNK_SIZE:
- num = 0
- for index in range(0, len(compressed_data), self.CHUNK_SIZE):
- sub_key = '%s-%s' % (key, num)
- all_data[sub_key] = compressed_data[index : index + self.CHUNK_SIZE]
- num += 1
-
- all_data[key] = _CachedItemMetaData(num)
- else:
- all_data[key] = compressed_data
-
- keys_not_set = memcache.set_multi(all_data, time=expire_time)
- return len(keys_not_set) == 0
-
-
-def _DefaultKeyGenerator(func, args, kwargs):
- """Generates a key from the function and arguments passed to it.
-
- Args:
- func (function): An arbitrary function.
- args (list): Positional arguments passed to ``func``.
- kwargs (dict): Keyword arguments passed to ``func``.
-
- Returns:
- A string to represent a call to the given function with the given arguments.
- """
- params = inspect.getcallargs(func, *args, **kwargs)
- for var_name in params:
- if not hasattr(params[var_name], 'identifier'):
- continue
-
- if callable(params[var_name].identifier):
- params[var_name] = params[var_name].identifier()
- else:
- params[var_name] = params[var_name].identifier
-
- return hashlib.md5(pickle.dumps(params)).hexdigest()
-
-
-def Cached(namespace=None,
- expire_time=0,
- key_generator=_DefaultKeyGenerator,
- cacher=PickledMemCacher()):
- """Returns a decorator to cache the decorated function's results.
-
- 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.
-
- The usage of this decorator requires that:
- - If the default key generator is used, parameters passed to the decorated
- function should be pickleable, or each of the parameter has an identifier
- property or method which returns pickleable results.
- - If the default cacher 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
- 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 1 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`.
- cacher (Cacher): An instance of an implementation of interface `Cacher`.
- Defaults to one of `PickledMemCacher` which is based on memcache.
-
- Returns:
- The cached results or the results of a new run of the decorated function.
- """
- def GetPrefix(func, namespace):
- return namespace or '%s.%s' % (func.__module__, func.__name__)
-
- def Decorator(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 = cacher.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))
-
- if result is not None:
- return result
-
- result = func(*args, **kwargs)
- if result:
- try:
- cacher.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))
-
- return result
-
- return Wrapped
-
- return Decorator
« no previous file with comments | « appengine/findit/common/blame.py ('k') | appengine/findit/common/change_log.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698