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

Unified Diff: third_party/google-endpoints/dogpile/cache/region.py

Issue 2666783008: Add google-endpoints to third_party/. (Closed)
Patch Set: 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 side-by-side diff with in-line comments
Download patch
Index: third_party/google-endpoints/dogpile/cache/region.py
diff --git a/third_party/google-endpoints/dogpile/cache/region.py b/third_party/google-endpoints/dogpile/cache/region.py
new file mode 100644
index 0000000000000000000000000000000000000000..76d39eed393350171c588f61022e00d384bb01c9
--- /dev/null
+++ b/third_party/google-endpoints/dogpile/cache/region.py
@@ -0,0 +1,1468 @@
+from __future__ import with_statement
+from .. import Lock, NeedRegenerationException
+from ..util import NameRegistry
+from . import exception
+from ..util import PluginLoader, memoized_property, coerce_string_conf
+from .util import function_key_generator, function_multi_key_generator
+from .api import NO_VALUE, CachedValue
+from .proxy import ProxyBackend
+from ..util import compat
+import time
+import datetime
+from numbers import Number
+from functools import wraps
+import threading
+
+_backend_loader = PluginLoader("dogpile.cache")
+register_backend = _backend_loader.register
+from . import backends # noqa
+
+value_version = 1
+"""An integer placed in the :class:`.CachedValue`
+so that new versions of dogpile.cache can detect cached
+values from a previous, backwards-incompatible version.
+
+"""
+
+
+class RegionInvalidationStrategy(object):
+ """Region invalidation strategy interface
+
+ Implement this interface and pass implementation instance
+ to :meth:`.CacheRegion.configure` to override default region invalidation.
+
+ Example::
+
+ class CustomInvalidationStrategy(RegionInvalidationStrategy):
+
+ def __init__(self):
+ self._soft_invalidated = None
+ self._hard_invalidated = None
+
+ def invalidate(self, hard=None):
+ if hard:
+ self._soft_invalidated = None
+ self._hard_invalidated = time.time()
+ else:
+ self._soft_invalidated = time.time()
+ self._hard_invalidated = None
+
+ def is_invalidated(self, timestamp):
+ return ((self._soft_invalidated and
+ timestamp < self._soft_invalidated) or
+ (self._hard_invalidated and
+ timestamp < self._hard_invalidated))
+
+ def was_hard_invalidated(self):
+ return bool(self._hard_invalidated)
+
+ def is_hard_invalidated(self, timestamp):
+ return (self._hard_invalidated and
+ timestamp < self._hard_invalidated)
+
+ def was_soft_invalidated(self):
+ return bool(self._soft_invalidated)
+
+ def is_soft_invalidated(self, timestamp):
+ return (self._soft_invalidated and
+ timestamp < self._soft_invalidated)
+
+ The custom implementation is injected into a :class:`.CacheRegion`
+ at configure time using the
+ :paramref:`.CacheRegion.configure.region_invalidator` parameter::
+
+ region = CacheRegion()
+
+ region = region.configure(region_invalidator=CustomInvalidationStrategy())
+
+ Invalidation strategies that wish to have access to the
+ :class:`.CacheRegion` itself should construct the invalidator given the
+ region as an argument::
+
+ class MyInvalidator(RegionInvalidationStrategy):
+ def __init__(self, region):
+ self.region = region
+ # ...
+
+ # ...
+
+ region = CacheRegion()
+ region = region.configure(region_invalidator=MyInvalidator(region))
+
+ .. versionadded:: 0.6.2
+
+ .. seealso::
+
+ :paramref:`.CacheRegion.configure.region_invalidator`
+
+ """
+
+ def invalidate(self, hard=True):
+ """Region invalidation.
+
+ :class:`.CacheRegion` propagated call.
+ The default invalidation system works by setting
+ a current timestamp (using ``time.time()``) to consider all older
+ timestamps effectively invalidated.
+
+ """
+
+ raise NotImplementedError()
+
+ def is_hard_invalidated(self, timestamp):
+ """Check timestamp to determine if it was hard invalidated.
+
+ :return: Boolean. True if ``timestamp`` is older than
+ the last region invalidation time and region is invalidated
+ in hard mode.
+
+ """
+
+ raise NotImplementedError()
+
+ def is_soft_invalidated(self, timestamp):
+ """Check timestamp to determine if it was soft invalidated.
+
+ :return: Boolean. True if ``timestamp`` is older than
+ the last region invalidation time and region is invalidated
+ in soft mode.
+
+ """
+
+ raise NotImplementedError()
+
+ def is_invalidated(self, timestamp):
+ """Check timestamp to determine if it was invalidated.
+
+ :return: Boolean. True if ``timestamp`` is older than
+ the last region invalidation time.
+
+ """
+
+ raise NotImplementedError()
+
+ def was_soft_invalidated(self):
+ """Indicate the region was invalidated in soft mode.
+
+ :return: Boolean. True if region was invalidated in soft mode.
+
+ """
+
+ raise NotImplementedError()
+
+ def was_hard_invalidated(self):
+ """Indicate the region was invalidated in hard mode.
+
+ :return: Boolean. True if region was invalidated in hard mode.
+
+ """
+
+ raise NotImplementedError()
+
+
+class DefaultInvalidationStrategy(RegionInvalidationStrategy):
+
+ def __init__(self):
+ self._is_hard_invalidated = None
+ self._invalidated = None
+
+ def invalidate(self, hard=True):
+ self._is_hard_invalidated = bool(hard)
+ self._invalidated = time.time()
+
+ def is_invalidated(self, timestamp):
+ return (self._invalidated is not None and
+ timestamp < self._invalidated)
+
+ def was_hard_invalidated(self):
+ return self._is_hard_invalidated is True
+
+ def is_hard_invalidated(self, timestamp):
+ return self.was_hard_invalidated() and self.is_invalidated(timestamp)
+
+ def was_soft_invalidated(self):
+ return self._is_hard_invalidated is False
+
+ def is_soft_invalidated(self, timestamp):
+ return self.was_soft_invalidated() and self.is_invalidated(timestamp)
+
+
+class CacheRegion(object):
+ """A front end to a particular cache backend.
+
+ :param name: Optional, a string name for the region.
+ This isn't used internally
+ but can be accessed via the ``.name`` parameter, helpful
+ for configuring a region from a config file.
+ :param function_key_generator: Optional. A
+ function that will produce a "cache key" given
+ a data creation function and arguments, when using
+ the :meth:`.CacheRegion.cache_on_arguments` method.
+ The structure of this function
+ should be two levels: given the data creation function,
+ return a new function that generates the key based on
+ the given arguments. Such as::
+
+ def my_key_generator(namespace, fn, **kw):
+ fname = fn.__name__
+ def generate_key(*arg):
+ return namespace + "_" + fname + "_".join(str(s) for s in arg)
+ return generate_key
+
+
+ region = make_region(
+ function_key_generator = my_key_generator
+ ).configure(
+ "dogpile.cache.dbm",
+ expiration_time=300,
+ arguments={
+ "filename":"file.dbm"
+ }
+ )
+
+ The ``namespace`` is that passed to
+ :meth:`.CacheRegion.cache_on_arguments`. It's not consulted
+ outside this function, so in fact can be of any form.
+ For example, it can be passed as a tuple, used to specify
+ arguments to pluck from \**kw::
+
+ def my_key_generator(namespace, fn):
+ def generate_key(*arg, **kw):
+ return ":".join(
+ [kw[k] for k in namespace] +
+ [str(x) for x in arg]
+ )
+ return generate_key
+
+
+ Where the decorator might be used as::
+
+ @my_region.cache_on_arguments(namespace=('x', 'y'))
+ def my_function(a, b, **kw):
+ return my_data()
+
+ .. seealso::
+
+ :func:`.function_key_generator` - default key generator
+
+ :func:`.kwarg_function_key_generator` - optional gen that also
+ uses keyword arguments
+
+ :param function_multi_key_generator: Optional.
+ Similar to ``function_key_generator`` parameter, but it's used in
+ :meth:`.CacheRegion.cache_multi_on_arguments`. Generated function
+ should return list of keys. For example::
+
+ def my_multi_key_generator(namespace, fn, **kw):
+ namespace = fn.__name__ + (namespace or '')
+
+ def generate_keys(*args):
+ return [namespace + ':' + str(a) for a in args]
+
+ return generate_keys
+
+ :param key_mangler: Function which will be used on all incoming
+ keys before passing to the backend. Defaults to ``None``,
+ in which case the key mangling function recommended by
+ the cache backend will be used. A typical mangler
+ is the SHA1 mangler found at :func:`.sha1_mangle_key`
+ which coerces keys into a SHA1
+ hash, so that the string length is fixed. To
+ disable all key mangling, set to ``False``. Another typical
+ mangler is the built-in Python function ``str``, which can be used
+ to convert non-string or Unicode keys to bytestrings, which is
+ needed when using a backend such as bsddb or dbm under Python 2.x
+ in conjunction with Unicode keys.
+ :param async_creation_runner: A callable that, when specified,
+ will be passed to and called by dogpile.lock when
+ there is a stale value present in the cache. It will be passed the
+ mutex and is responsible releasing that mutex when finished.
+ This can be used to defer the computation of expensive creator
+ functions to later points in the future by way of, for example, a
+ background thread, a long-running queue, or a task manager system
+ like Celery.
+
+ For a specific example using async_creation_runner, new values can
+ be created in a background thread like so::
+
+ import threading
+
+ def async_creation_runner(cache, somekey, creator, mutex):
+ ''' Used by dogpile.core:Lock when appropriate '''
+ def runner():
+ try:
+ value = creator()
+ cache.set(somekey, value)
+ finally:
+ mutex.release()
+
+ thread = threading.Thread(target=runner)
+ thread.start()
+
+
+ region = make_region(
+ async_creation_runner=async_creation_runner,
+ ).configure(
+ 'dogpile.cache.memcached',
+ expiration_time=5,
+ arguments={
+ 'url': '127.0.0.1:11211',
+ 'distributed_lock': True,
+ }
+ )
+
+ Remember that the first request for a key with no associated
+ value will always block; async_creator will not be invoked.
+ However, subsequent requests for cached-but-expired values will
+ still return promptly. They will be refreshed by whatever
+ asynchronous means the provided async_creation_runner callable
+ implements.
+
+ By default the async_creation_runner is disabled and is set
+ to ``None``.
+
+ .. versionadded:: 0.4.2 added the async_creation_runner
+ feature.
+
+ """
+
+ def __init__(
+ self,
+ name=None,
+ function_key_generator=function_key_generator,
+ function_multi_key_generator=function_multi_key_generator,
+ key_mangler=None,
+ async_creation_runner=None,
+ ):
+ """Construct a new :class:`.CacheRegion`."""
+ self.name = name
+ self.function_key_generator = function_key_generator
+ self.function_multi_key_generator = function_multi_key_generator
+ self.key_mangler = self._user_defined_key_mangler = key_mangler
+ self.async_creation_runner = async_creation_runner
+ self.region_invalidator = DefaultInvalidationStrategy()
+
+ def configure(
+ self, backend,
+ expiration_time=None,
+ arguments=None,
+ _config_argument_dict=None,
+ _config_prefix=None,
+ wrap=None,
+ replace_existing_backend=False,
+ region_invalidator=None
+ ):
+ """Configure a :class:`.CacheRegion`.
+
+ The :class:`.CacheRegion` itself
+ is returned.
+
+ :param backend: Required. This is the name of the
+ :class:`.CacheBackend` to use, and is resolved by loading
+ the class from the ``dogpile.cache`` entrypoint.
+
+ :param expiration_time: Optional. The expiration time passed
+ to the dogpile system. May be passed as an integer number
+ of seconds, or as a ``datetime.timedelta`` value.
+
+ .. versionadded 0.5.0
+ ``expiration_time`` may be optionally passed as a
+ ``datetime.timedelta`` value.
+
+ The :meth:`.CacheRegion.get_or_create`
+ method as well as the :meth:`.CacheRegion.cache_on_arguments`
+ decorator (though note: **not** the :meth:`.CacheRegion.get`
+ method) will call upon the value creation function after this
+ time period has passed since the last generation.
+
+ :param arguments: Optional. The structure here is passed
+ directly to the constructor of the :class:`.CacheBackend`
+ in use, though is typically a dictionary.
+
+ :param wrap: Optional. A list of :class:`.ProxyBackend`
+ classes and/or instances, each of which will be applied
+ in a chain to ultimately wrap the original backend,
+ so that custom functionality augmentation can be applied.
+
+ .. versionadded:: 0.5.0
+
+ .. seealso::
+
+ :ref:`changing_backend_behavior`
+
+ :param replace_existing_backend: if True, the existing cache backend
+ will be replaced. Without this flag, an exception is raised if
+ a backend is already configured.
+
+ .. versionadded:: 0.5.7
+
+ :param region_invalidator: Optional. Override default invalidation
+ strategy with custom implementation of
+ :class:`.RegionInvalidationStrategy`.
+
+ .. versionadded:: 0.6.2
+
+ """
+
+ if "backend" in self.__dict__ and not replace_existing_backend:
+ raise exception.RegionAlreadyConfigured(
+ "This region is already "
+ "configured with backend: %s. "
+ "Specify replace_existing_backend=True to replace."
+ % self.backend)
+ backend_cls = _backend_loader.load(backend)
+ if _config_argument_dict:
+ self.backend = backend_cls.from_config_dict(
+ _config_argument_dict,
+ _config_prefix
+ )
+ else:
+ self.backend = backend_cls(arguments or {})
+
+ if not expiration_time or isinstance(expiration_time, Number):
+ self.expiration_time = expiration_time
+ elif isinstance(expiration_time, datetime.timedelta):
+ self.expiration_time = int(
+ compat.timedelta_total_seconds(expiration_time))
+ else:
+ raise exception.ValidationError(
+ 'expiration_time is not a number or timedelta.')
+
+ if not self._user_defined_key_mangler:
+ self.key_mangler = self.backend.key_mangler
+
+ self._lock_registry = NameRegistry(self._create_mutex)
+
+ if getattr(wrap, '__iter__', False):
+ for wrapper in reversed(wrap):
+ self.wrap(wrapper)
+
+ if region_invalidator:
+ self.region_invalidator = region_invalidator
+
+ return self
+
+ def wrap(self, proxy):
+ ''' Takes a ProxyBackend instance or class and wraps the
+ attached backend. '''
+
+ # if we were passed a type rather than an instance then
+ # initialize it.
+ if type(proxy) == type:
+ proxy = proxy()
+
+ if not issubclass(type(proxy), ProxyBackend):
+ raise TypeError("Type %s is not a valid ProxyBackend"
+ % type(proxy))
+
+ self.backend = proxy.wrap(self.backend)
+
+ def _mutex(self, key):
+ return self._lock_registry.get(key)
+
+ class _LockWrapper(object):
+ """weakref-capable wrapper for threading.Lock"""
+ def __init__(self):
+ self.lock = threading.Lock()
+
+ def acquire(self, wait=True):
+ return self.lock.acquire(wait)
+
+ def release(self):
+ self.lock.release()
+
+ def _create_mutex(self, key):
+ mutex = self.backend.get_mutex(key)
+ if mutex is not None:
+ return mutex
+ else:
+ return self._LockWrapper()
+
+ def invalidate(self, hard=True):
+ """Invalidate this :class:`.CacheRegion`.
+
+ The default invalidation system works by setting
+ a current timestamp (using ``time.time()``)
+ representing the "minimum creation time" for
+ a value. Any retrieved value whose creation
+ time is prior to this timestamp
+ is considered to be stale. It does not
+ affect the data in the cache in any way, and is also
+ local to this instance of :class:`.CacheRegion`.
+
+ Once set, the invalidation time is honored by
+ the :meth:`.CacheRegion.get_or_create`,
+ :meth:`.CacheRegion.get_or_create_multi` and
+ :meth:`.CacheRegion.get` methods.
+
+ The method supports both "hard" and "soft" invalidation
+ options. With "hard" invalidation,
+ :meth:`.CacheRegion.get_or_create` will force an immediate
+ regeneration of the value which all getters will wait for.
+ With "soft" invalidation, subsequent getters will return the
+ "old" value until the new one is available.
+
+ Usage of "soft" invalidation requires that the region or the method
+ is given a non-None expiration time.
+
+ .. versionadded:: 0.3.0
+
+ :param hard: if True, cache values will all require immediate
+ regeneration; dogpile logic won't be used. If False, the
+ creation time of existing values will be pushed back before
+ the expiration time so that a return+regen will be invoked.
+
+ .. versionadded:: 0.5.1
+
+ """
+ self.region_invalidator.invalidate(hard)
+
+ def configure_from_config(self, config_dict, prefix):
+ """Configure from a configuration dictionary
+ and a prefix.
+
+ Example::
+
+ local_region = make_region()
+ memcached_region = make_region()
+
+ # regions are ready to use for function
+ # decorators, but not yet for actual caching
+
+ # later, when config is available
+ myconfig = {
+ "cache.local.backend":"dogpile.cache.dbm",
+ "cache.local.arguments.filename":"/path/to/dbmfile.dbm",
+ "cache.memcached.backend":"dogpile.cache.pylibmc",
+ "cache.memcached.arguments.url":"127.0.0.1, 10.0.0.1",
+ }
+ local_region.configure_from_config(myconfig, "cache.local.")
+ memcached_region.configure_from_config(myconfig,
+ "cache.memcached.")
+
+ """
+ config_dict = coerce_string_conf(config_dict)
+ return self.configure(
+ config_dict["%sbackend" % prefix],
+ expiration_time=config_dict.get(
+ "%sexpiration_time" % prefix, None),
+ _config_argument_dict=config_dict,
+ _config_prefix="%sarguments." % prefix,
+ wrap=config_dict.get(
+ "%swrap" % prefix, None),
+ )
+
+ @memoized_property
+ def backend(self):
+ raise exception.RegionNotConfigured(
+ "No backend is configured on this region.")
+
+ @property
+ def is_configured(self):
+ """Return True if the backend has been configured via the
+ :meth:`.CacheRegion.configure` method already.
+
+ .. versionadded:: 0.5.1
+
+ """
+ return 'backend' in self.__dict__
+
+ def get(self, key, expiration_time=None, ignore_expiration=False):
+ """Return a value from the cache, based on the given key.
+
+ If the value is not present, the method returns the token
+ ``NO_VALUE``. ``NO_VALUE`` evaluates to False, but is separate from
+ ``None`` to distinguish between a cached value of ``None``.
+
+ By default, the configured expiration time of the
+ :class:`.CacheRegion`, or alternatively the expiration
+ time supplied by the ``expiration_time`` argument,
+ is tested against the creation time of the retrieved
+ value versus the current time (as reported by ``time.time()``).
+ If stale, the cached value is ignored and the ``NO_VALUE``
+ token is returned. Passing the flag ``ignore_expiration=True``
+ bypasses the expiration time check.
+
+ .. versionchanged:: 0.3.0
+ :meth:`.CacheRegion.get` now checks the value's creation time
+ against the expiration time, rather than returning
+ the value unconditionally.
+
+ The method also interprets the cached value in terms
+ of the current "invalidation" time as set by
+ the :meth:`.invalidate` method. If a value is present,
+ but its creation time is older than the current
+ invalidation time, the ``NO_VALUE`` token is returned.
+ Passing the flag ``ignore_expiration=True`` bypasses
+ the invalidation time check.
+
+ .. versionadded:: 0.3.0
+ Support for the :meth:`.CacheRegion.invalidate`
+ method.
+
+ :param key: Key to be retrieved. While it's typical for a key to be a
+ string, it is ultimately passed directly down to the cache backend,
+ before being optionally processed by the key_mangler function, so can
+ be of any type recognized by the backend or by the key_mangler
+ function, if present.
+
+ :param expiration_time: Optional expiration time value
+ which will supersede that configured on the :class:`.CacheRegion`
+ itself.
+
+ .. versionadded:: 0.3.0
+
+ :param ignore_expiration: if ``True``, the value is returned
+ from the cache if present, regardless of configured
+ expiration times or whether or not :meth:`.invalidate`
+ was called.
+
+ .. versionadded:: 0.3.0
+
+ """
+
+ if self.key_mangler:
+ key = self.key_mangler(key)
+ value = self.backend.get(key)
+ value = self._unexpired_value_fn(
+ expiration_time, ignore_expiration)(value)
+
+ return value.payload
+
+ def _unexpired_value_fn(self, expiration_time, ignore_expiration):
+ if ignore_expiration:
+ return lambda value: value
+ else:
+ if expiration_time is None:
+ expiration_time = self.expiration_time
+
+ current_time = time.time()
+
+ def value_fn(value):
+ if value is NO_VALUE:
+ return value
+ elif expiration_time is not None and \
+ current_time - value.metadata["ct"] > expiration_time:
+ return NO_VALUE
+ elif self.region_invalidator.is_invalidated(
+ value.metadata["ct"]):
+ return NO_VALUE
+ else:
+ return value
+
+ return value_fn
+
+ def get_multi(self, keys, expiration_time=None, ignore_expiration=False):
+ """Return multiple values from the cache, based on the given keys.
+
+ Returns values as a list matching the keys given.
+
+ E.g.::
+
+ values = region.get_multi(["one", "two", "three"])
+
+ To convert values to a dictionary, use ``zip()``::
+
+ keys = ["one", "two", "three"]
+ values = region.get_multi(keys)
+ dictionary = dict(zip(keys, values))
+
+ Keys which aren't present in the list are returned as
+ the ``NO_VALUE`` token. ``NO_VALUE`` evaluates to False,
+ but is separate from
+ ``None`` to distinguish between a cached value of ``None``.
+
+ By default, the configured expiration time of the
+ :class:`.CacheRegion`, or alternatively the expiration
+ time supplied by the ``expiration_time`` argument,
+ is tested against the creation time of the retrieved
+ value versus the current time (as reported by ``time.time()``).
+ If stale, the cached value is ignored and the ``NO_VALUE``
+ token is returned. Passing the flag ``ignore_expiration=True``
+ bypasses the expiration time check.
+
+ .. versionadded:: 0.5.0
+
+ """
+ if not keys:
+ return []
+
+ if self.key_mangler:
+ keys = list(map(lambda key: self.key_mangler(key), keys))
+
+ backend_values = self.backend.get_multi(keys)
+
+ _unexpired_value_fn = self._unexpired_value_fn(
+ expiration_time, ignore_expiration)
+ return [
+ value.payload if value is not NO_VALUE else value
+ for value in
+ (
+ _unexpired_value_fn(value) for value in
+ backend_values
+ )
+ ]
+
+ def get_or_create(
+ self, key, creator, expiration_time=None, should_cache_fn=None):
+ """Return a cached value based on the given key.
+
+ If the value does not exist or is considered to be expired
+ based on its creation time, the given
+ creation function may or may not be used to recreate the value
+ and persist the newly generated value in the cache.
+
+ Whether or not the function is used depends on if the
+ *dogpile lock* can be acquired or not. If it can't, it means
+ a different thread or process is already running a creation
+ function for this key against the cache. When the dogpile
+ lock cannot be acquired, the method will block if no
+ previous value is available, until the lock is released and
+ a new value available. If a previous value
+ is available, that value is returned immediately without blocking.
+
+ If the :meth:`.invalidate` method has been called, and
+ the retrieved value's timestamp is older than the invalidation
+ timestamp, the value is unconditionally prevented from
+ being returned. The method will attempt to acquire the dogpile
+ lock to generate a new value, or will wait
+ until the lock is released to return the new value.
+
+ .. versionchanged:: 0.3.0
+ The value is unconditionally regenerated if the creation
+ time is older than the last call to :meth:`.invalidate`.
+
+ :param key: Key to be retrieved. While it's typical for a key to be a
+ string, it is ultimately passed directly down to the cache backend,
+ before being optionally processed by the key_mangler function, so can
+ be of any type recognized by the backend or by the key_mangler
+ function, if present.
+
+ :param creator: function which creates a new value.
+
+ :param expiration_time: optional expiration time which will overide
+ the expiration time already configured on this :class:`.CacheRegion`
+ if not None. To set no expiration, use the value -1.
+
+ :param should_cache_fn: optional callable function which will receive
+ the value returned by the "creator", and will then return True or
+ False, indicating if the value should actually be cached or not. If
+ it returns False, the value is still returned, but isn't cached.
+ E.g.::
+
+ def dont_cache_none(value):
+ return value is not None
+
+ value = region.get_or_create("some key",
+ create_value,
+ should_cache_fn=dont_cache_none)
+
+ Above, the function returns the value of create_value() if
+ the cache is invalid, however if the return value is None,
+ it won't be cached.
+
+ .. versionadded:: 0.4.3
+
+ .. seealso::
+
+ :meth:`.CacheRegion.cache_on_arguments` - applies
+ :meth:`.get_or_create` to any function using a decorator.
+
+ :meth:`.CacheRegion.get_or_create_multi` - multiple key/value
+ version
+
+ """
+ orig_key = key
+ if self.key_mangler:
+ key = self.key_mangler(key)
+
+ def get_value():
+ value = self.backend.get(key)
+ if (value is NO_VALUE or value.metadata['v'] != value_version or
+ self.region_invalidator.is_hard_invalidated(
+ value.metadata["ct"])):
+ raise NeedRegenerationException()
+ ct = value.metadata["ct"]
+ if self.region_invalidator.is_soft_invalidated(ct):
+ ct = time.time() - expiration_time - .0001
+
+ return value.payload, ct
+
+ def gen_value():
+ created_value = creator()
+ value = self._value(created_value)
+
+ if not should_cache_fn or \
+ should_cache_fn(created_value):
+ self.backend.set(key, value)
+
+ return value.payload, value.metadata["ct"]
+
+ if expiration_time is None:
+ expiration_time = self.expiration_time
+
+ if (expiration_time is None and
+ self.region_invalidator.was_soft_invalidated()):
+ raise exception.DogpileCacheException(
+ "Non-None expiration time required "
+ "for soft invalidation")
+
+ if expiration_time == -1:
+ expiration_time = None
+
+ if self.async_creation_runner:
+ def async_creator(mutex):
+ return self.async_creation_runner(
+ self, orig_key, creator, mutex)
+ else:
+ async_creator = None
+
+ with Lock(
+ self._mutex(key),
+ gen_value,
+ get_value,
+ expiration_time,
+ async_creator) as value:
+ return value
+
+ def get_or_create_multi(
+ self, keys, creator, expiration_time=None, should_cache_fn=None):
+ """Return a sequence of cached values based on a sequence of keys.
+
+ The behavior for generation of values based on keys corresponds
+ to that of :meth:`.Region.get_or_create`, with the exception that
+ the ``creator()`` function may be asked to generate any subset of
+ the given keys. The list of keys to be generated is passed to
+ ``creator()``, and ``creator()`` should return the generated values
+ as a sequence corresponding to the order of the keys.
+
+ The method uses the same approach as :meth:`.Region.get_multi`
+ and :meth:`.Region.set_multi` to get and set values from the
+ backend.
+
+ If you are using a :class:`.CacheBackend` or :class:`.ProxyBackend`
+ that modifies values, take note this function invokes
+ ``.set_multi()`` for newly generated values using the same values it
+ returns to the calling function. A correct implementation of
+ ``.set_multi()`` will not modify values in-place on the submitted
+ ``mapping`` dict.
+
+ :param keys: Sequence of keys to be retrieved.
+
+ :param creator: function which accepts a sequence of keys and
+ returns a sequence of new values.
+
+ :param expiration_time: optional expiration time which will overide
+ the expiration time already configured on this :class:`.CacheRegion`
+ if not None. To set no expiration, use the value -1.
+
+ :param should_cache_fn: optional callable function which will receive
+ each value returned by the "creator", and will then return True or
+ False, indicating if the value should actually be cached or not. If
+ it returns False, the value is still returned, but isn't cached.
+
+ .. versionadded:: 0.5.0
+
+ .. seealso::
+
+
+ :meth:`.CacheRegion.cache_multi_on_arguments`
+
+ :meth:`.CacheRegion.get_or_create`
+
+ """
+
+ def get_value(key):
+ value = values.get(key, NO_VALUE)
+
+ if (value is NO_VALUE or value.metadata['v'] != value_version or
+ self.region_invalidator.is_hard_invalidated(
+ value.metadata['v'])):
+ # dogpile.core understands a 0 here as
+ # "the value is not available", e.g.
+ # _has_value() will return False.
+ return value.payload, 0
+ else:
+ ct = value.metadata["ct"]
+ if self.region_invalidator.is_soft_invalidated(ct):
+ ct = time.time() - expiration_time - .0001
+
+ return value.payload, ct
+
+ def gen_value():
+ raise NotImplementedError()
+
+ def async_creator(key, mutex):
+ mutexes[key] = mutex
+
+ if expiration_time is None:
+ expiration_time = self.expiration_time
+
+ if (expiration_time is None and
+ self.region_invalidator.was_soft_invalidated()):
+ raise exception.DogpileCacheException(
+ "Non-None expiration time required "
+ "for soft invalidation")
+
+ if expiration_time == -1:
+ expiration_time = None
+
+ mutexes = {}
+
+ sorted_unique_keys = sorted(set(keys))
+
+ if self.key_mangler:
+ mangled_keys = [self.key_mangler(k) for k in sorted_unique_keys]
+ else:
+ mangled_keys = sorted_unique_keys
+
+ orig_to_mangled = dict(zip(sorted_unique_keys, mangled_keys))
+
+ values = dict(zip(mangled_keys, self.backend.get_multi(mangled_keys)))
+
+ for orig_key, mangled_key in orig_to_mangled.items():
+ with Lock(
+ self._mutex(mangled_key),
+ gen_value,
+ lambda: get_value(mangled_key),
+ expiration_time,
+ async_creator=lambda mutex: async_creator(orig_key, mutex)
+ ):
+ pass
+ try:
+ if mutexes:
+ # sort the keys, the idea is to prevent deadlocks.
+ # though haven't been able to simulate one anyway.
+ keys_to_get = sorted(mutexes)
+ new_values = creator(*keys_to_get)
+
+ values_w_created = dict(
+ (orig_to_mangled[k], self._value(v))
+ for k, v in zip(keys_to_get, new_values)
+ )
+
+ if not should_cache_fn:
+ self.backend.set_multi(values_w_created)
+ else:
+ self.backend.set_multi(dict(
+ (k, v)
+ for k, v in values_w_created.items()
+ if should_cache_fn(v[0])
+ ))
+
+ values.update(values_w_created)
+ return [values[orig_to_mangled[k]].payload for k in keys]
+ finally:
+ for mutex in mutexes.values():
+ mutex.release()
+
+ def _value(self, value):
+ """Return a :class:`.CachedValue` given a value."""
+ return CachedValue(
+ value,
+ {
+ "ct": time.time(),
+ "v": value_version
+ })
+
+ def set(self, key, value):
+ """Place a new value in the cache under the given key."""
+
+ if self.key_mangler:
+ key = self.key_mangler(key)
+ self.backend.set(key, self._value(value))
+
+ def set_multi(self, mapping):
+ """Place new values in the cache under the given keys.
+
+ .. versionadded:: 0.5.0
+
+ """
+ if not mapping:
+ return
+
+ if self.key_mangler:
+ mapping = dict((
+ self.key_mangler(k), self._value(v))
+ for k, v in mapping.items())
+ else:
+ mapping = dict((k, self._value(v)) for k, v in mapping.items())
+ self.backend.set_multi(mapping)
+
+ def delete(self, key):
+ """Remove a value from the cache.
+
+ This operation is idempotent (can be called multiple times, or on a
+ non-existent key, safely)
+ """
+
+ if self.key_mangler:
+ key = self.key_mangler(key)
+
+ self.backend.delete(key)
+
+ def delete_multi(self, keys):
+ """Remove multiple values from the cache.
+
+ This operation is idempotent (can be called multiple times, or on a
+ non-existent key, safely)
+
+ .. versionadded:: 0.5.0
+
+ """
+
+ if self.key_mangler:
+ keys = list(map(lambda key: self.key_mangler(key), keys))
+
+ self.backend.delete_multi(keys)
+
+ def cache_on_arguments(
+ self, namespace=None,
+ expiration_time=None,
+ should_cache_fn=None,
+ to_str=compat.string_type,
+ function_key_generator=None):
+ """A function decorator that will cache the return
+ value of the function using a key derived from the
+ function itself and its arguments.
+
+ The decorator internally makes use of the
+ :meth:`.CacheRegion.get_or_create` method to access the
+ cache and conditionally call the function. See that
+ method for additional behavioral details.
+
+ E.g.::
+
+ @someregion.cache_on_arguments()
+ def generate_something(x, y):
+ return somedatabase.query(x, y)
+
+ The decorated function can then be called normally, where
+ data will be pulled from the cache region unless a new
+ value is needed::
+
+ result = generate_something(5, 6)
+
+ The function is also given an attribute ``invalidate()``, which
+ provides for invalidation of the value. Pass to ``invalidate()``
+ the same arguments you'd pass to the function itself to represent
+ a particular value::
+
+ generate_something.invalidate(5, 6)
+
+ Another attribute ``set()`` is added to provide extra caching
+ possibilities relative to the function. This is a convenience
+ method for :meth:`.CacheRegion.set` which will store a given
+ value directly without calling the decorated function.
+ The value to be cached is passed as the first argument, and the
+ arguments which would normally be passed to the function
+ should follow::
+
+ generate_something.set(3, 5, 6)
+
+ The above example is equivalent to calling
+ ``generate_something(5, 6)``, if the function were to produce
+ the value ``3`` as the value to be cached.
+
+ .. versionadded:: 0.4.1 Added ``set()`` method to decorated function.
+
+ Similar to ``set()`` is ``refresh()``. This attribute will
+ invoke the decorated function and populate a new value into
+ the cache with the new value, as well as returning that value::
+
+ newvalue = generate_something.refresh(5, 6)
+
+ .. versionadded:: 0.5.0 Added ``refresh()`` method to decorated
+ function.
+
+ Lastly, the ``get()`` method returns either the value cached
+ for the given key, or the token ``NO_VALUE`` if no such key
+ exists::
+
+ value = generate_something.get(5, 6)
+
+ .. versionadded:: 0.5.3 Added ``get()`` method to decorated
+ function.
+
+ The default key generation will use the name
+ of the function, the module name for the function,
+ the arguments passed, as well as an optional "namespace"
+ parameter in order to generate a cache key.
+
+ Given a function ``one`` inside the module
+ ``myapp.tools``::
+
+ @region.cache_on_arguments(namespace="foo")
+ def one(a, b):
+ return a + b
+
+ Above, calling ``one(3, 4)`` will produce a
+ cache key as follows::
+
+ myapp.tools:one|foo|3 4
+
+ The key generator will ignore an initial argument
+ of ``self`` or ``cls``, making the decorator suitable
+ (with caveats) for use with instance or class methods.
+ Given the example::
+
+ class MyClass(object):
+ @region.cache_on_arguments(namespace="foo")
+ def one(self, a, b):
+ return a + b
+
+ The cache key above for ``MyClass().one(3, 4)`` will
+ again produce the same cache key of ``myapp.tools:one|foo|3 4`` -
+ the name ``self`` is skipped.
+
+ The ``namespace`` parameter is optional, and is used
+ normally to disambiguate two functions of the same
+ name within the same module, as can occur when decorating
+ instance or class methods as below::
+
+ class MyClass(object):
+ @region.cache_on_arguments(namespace='MC')
+ def somemethod(self, x, y):
+ ""
+
+ class MyOtherClass(object):
+ @region.cache_on_arguments(namespace='MOC')
+ def somemethod(self, x, y):
+ ""
+
+ Above, the ``namespace`` parameter disambiguates
+ between ``somemethod`` on ``MyClass`` and ``MyOtherClass``.
+ Python class declaration mechanics otherwise prevent
+ the decorator from having awareness of the ``MyClass``
+ and ``MyOtherClass`` names, as the function is received
+ by the decorator before it becomes an instance method.
+
+ The function key generation can be entirely replaced
+ on a per-region basis using the ``function_key_generator``
+ argument present on :func:`.make_region` and
+ :class:`.CacheRegion`. If defaults to
+ :func:`.function_key_generator`.
+
+ :param namespace: optional string argument which will be
+ established as part of the cache key. This may be needed
+ to disambiguate functions of the same name within the same
+ source file, such as those
+ associated with classes - note that the decorator itself
+ can't see the parent class on a function as the class is
+ being declared.
+
+ :param expiration_time: if not None, will override the normal
+ expiration time.
+
+ May be specified as a callable, taking no arguments, that
+ returns a value to be used as the ``expiration_time``. This callable
+ will be called whenever the decorated function itself is called, in
+ caching or retrieving. Thus, this can be used to
+ determine a *dynamic* expiration time for the cached function
+ result. Example use cases include "cache the result until the
+ end of the day, week or time period" and "cache until a certain date
+ or time passes".
+
+ .. versionchanged:: 0.5.0
+ ``expiration_time`` may be passed as a callable to
+ :meth:`.CacheRegion.cache_on_arguments`.
+
+ :param should_cache_fn: passed to :meth:`.CacheRegion.get_or_create`.
+
+ .. versionadded:: 0.4.3
+
+ :param to_str: callable, will be called on each function argument
+ in order to convert to a string. Defaults to ``str()``. If the
+ function accepts non-ascii unicode arguments on Python 2.x, the
+ ``unicode()`` builtin can be substituted, but note this will
+ produce unicode cache keys which may require key mangling before
+ reaching the cache.
+
+ .. versionadded:: 0.5.0
+
+ :param function_key_generator: a function that will produce a
+ "cache key". This function will supersede the one configured on the
+ :class:`.CacheRegion` itself.
+
+ .. versionadded:: 0.5.5
+
+ .. seealso::
+
+ :meth:`.CacheRegion.cache_multi_on_arguments`
+
+ :meth:`.CacheRegion.get_or_create`
+
+ """
+ expiration_time_is_callable = compat.callable(expiration_time)
+
+ if function_key_generator is None:
+ function_key_generator = self.function_key_generator
+
+ def decorator(fn):
+ if to_str is compat.string_type:
+ # backwards compatible
+ key_generator = function_key_generator(namespace, fn)
+ else:
+ key_generator = function_key_generator(
+ namespace, fn,
+ to_str=to_str)
+
+ @wraps(fn)
+ def decorate(*arg, **kw):
+ key = key_generator(*arg, **kw)
+
+ @wraps(fn)
+ def creator():
+ return fn(*arg, **kw)
+ timeout = expiration_time() if expiration_time_is_callable \
+ else expiration_time
+ return self.get_or_create(key, creator, timeout,
+ should_cache_fn)
+
+ def invalidate(*arg, **kw):
+ key = key_generator(*arg, **kw)
+ self.delete(key)
+
+ def set_(value, *arg, **kw):
+ key = key_generator(*arg, **kw)
+ self.set(key, value)
+
+ def get(*arg, **kw):
+ key = key_generator(*arg, **kw)
+ return self.get(key)
+
+ def refresh(*arg, **kw):
+ key = key_generator(*arg, **kw)
+ value = fn(*arg, **kw)
+ self.set(key, value)
+ return value
+
+ decorate.set = set_
+ decorate.invalidate = invalidate
+ decorate.refresh = refresh
+ decorate.get = get
+ decorate.original = fn
+
+ return decorate
+ return decorator
+
+ def cache_multi_on_arguments(
+ self, namespace=None, expiration_time=None,
+ should_cache_fn=None,
+ asdict=False, to_str=compat.string_type,
+ function_multi_key_generator=None):
+ """A function decorator that will cache multiple return
+ values from the function using a sequence of keys derived from the
+ function itself and the arguments passed to it.
+
+ This method is the "multiple key" analogue to the
+ :meth:`.CacheRegion.cache_on_arguments` method.
+
+ Example::
+
+ @someregion.cache_multi_on_arguments()
+ def generate_something(*keys):
+ return [
+ somedatabase.query(key)
+ for key in keys
+ ]
+
+ The decorated function can be called normally. The decorator
+ will produce a list of cache keys using a mechanism similar to
+ that of :meth:`.CacheRegion.cache_on_arguments`, combining the
+ name of the function with the optional namespace and with the
+ string form of each key. It will then consult the cache using
+ the same mechanism as that of :meth:`.CacheRegion.get_multi`
+ to retrieve all current values; the originally passed keys
+ corresponding to those values which aren't generated or need
+ regeneration will be assembled into a new argument list, and
+ the decorated function is then called with that subset of
+ arguments.
+
+ The returned result is a list::
+
+ result = generate_something("key1", "key2", "key3")
+
+ The decorator internally makes use of the
+ :meth:`.CacheRegion.get_or_create_multi` method to access the
+ cache and conditionally call the function. See that
+ method for additional behavioral details.
+
+ Unlike the :meth:`.CacheRegion.cache_on_arguments` method,
+ :meth:`.CacheRegion.cache_multi_on_arguments` works only with
+ a single function signature, one which takes a simple list of
+ keys as arguments.
+
+ Like :meth:`.CacheRegion.cache_on_arguments`, the decorated function
+ is also provided with a ``set()`` method, which here accepts a
+ mapping of keys and values to set in the cache::
+
+ generate_something.set({"k1": "value1",
+ "k2": "value2", "k3": "value3"})
+
+ ...an ``invalidate()`` method, which has the effect of deleting
+ the given sequence of keys using the same mechanism as that of
+ :meth:`.CacheRegion.delete_multi`::
+
+ generate_something.invalidate("k1", "k2", "k3")
+
+ ...a ``refresh()`` method, which will call the creation
+ function, cache the new values, and return them::
+
+ values = generate_something.refresh("k1", "k2", "k3")
+
+ ...and a ``get()`` method, which will return values
+ based on the given arguments::
+
+ values = generate_something.get("k1", "k2", "k3")
+
+ .. versionadded:: 0.5.3 Added ``get()`` method to decorated
+ function.
+
+ Parameters passed to :meth:`.CacheRegion.cache_multi_on_arguments`
+ have the same meaning as those passed to
+ :meth:`.CacheRegion.cache_on_arguments`.
+
+ :param namespace: optional string argument which will be
+ established as part of each cache key.
+
+ :param expiration_time: if not None, will override the normal
+ expiration time. May be passed as an integer or a
+ callable.
+
+ :param should_cache_fn: passed to
+ :meth:`.CacheRegion.get_or_create_multi`. This function is given a
+ value as returned by the creator, and only if it returns True will
+ that value be placed in the cache.
+
+ :param asdict: if ``True``, the decorated function should return
+ its result as a dictionary of keys->values, and the final result
+ of calling the decorated function will also be a dictionary.
+ If left at its default value of ``False``, the decorated function
+ should return its result as a list of values, and the final
+ result of calling the decorated function will also be a list.
+
+ When ``asdict==True`` if the dictionary returned by the decorated
+ function is missing keys, those keys will not be cached.
+
+ :param to_str: callable, will be called on each function argument
+ in order to convert to a string. Defaults to ``str()``. If the
+ function accepts non-ascii unicode arguments on Python 2.x, the
+ ``unicode()`` builtin can be substituted, but note this will
+ produce unicode cache keys which may require key mangling before
+ reaching the cache.
+
+ .. versionadded:: 0.5.0
+
+ :param function_multi_key_generator: a function that will produce a
+ list of keys. This function will supersede the one configured on the
+ :class:`.CacheRegion` itself.
+
+ .. versionadded:: 0.5.5
+
+ .. seealso::
+
+ :meth:`.CacheRegion.cache_on_arguments`
+
+ :meth:`.CacheRegion.get_or_create_multi`
+
+ """
+ expiration_time_is_callable = compat.callable(expiration_time)
+
+ if function_multi_key_generator is None:
+ function_multi_key_generator = self.function_multi_key_generator
+
+ def decorator(fn):
+ key_generator = function_multi_key_generator(
+ namespace, fn,
+ to_str=to_str)
+
+ @wraps(fn)
+ def decorate(*arg, **kw):
+ cache_keys = arg
+ keys = key_generator(*arg, **kw)
+ key_lookup = dict(zip(keys, cache_keys))
+
+ @wraps(fn)
+ def creator(*keys_to_create):
+ return fn(*[key_lookup[k] for k in keys_to_create])
+
+ timeout = expiration_time() if expiration_time_is_callable \
+ else expiration_time
+
+ if asdict:
+ def dict_create(*keys):
+ d_values = creator(*keys)
+ return [
+ d_values.get(key_lookup[k], NO_VALUE)
+ for k in keys]
+
+ def wrap_cache_fn(value):
+ if value is NO_VALUE:
+ return False
+ elif not should_cache_fn:
+ return True
+ else:
+ return should_cache_fn(value)
+
+ result = self.get_or_create_multi(
+ keys, dict_create, timeout, wrap_cache_fn)
+ result = dict(
+ (k, v) for k, v in zip(cache_keys, result)
+ if v is not NO_VALUE)
+ else:
+ result = self.get_or_create_multi(
+ keys, creator, timeout,
+ should_cache_fn)
+
+ return result
+
+ def invalidate(*arg):
+ keys = key_generator(*arg)
+ self.delete_multi(keys)
+
+ def set_(mapping):
+ keys = list(mapping)
+ gen_keys = key_generator(*keys)
+ self.set_multi(dict(
+ (gen_key, mapping[key])
+ for gen_key, key
+ in zip(gen_keys, keys))
+ )
+
+ def get(*arg):
+ keys = key_generator(*arg)
+ return self.get_multi(keys)
+
+ def refresh(*arg):
+ keys = key_generator(*arg)
+ values = fn(*arg)
+ if asdict:
+ self.set_multi(
+ dict(zip(keys, [values[a] for a in arg]))
+ )
+ return values
+ else:
+ self.set_multi(
+ dict(zip(keys, values))
+ )
+ return values
+
+ decorate.set = set_
+ decorate.invalidate = invalidate
+ decorate.refresh = refresh
+ decorate.get = get
+
+ return decorate
+ return decorator
+
+
+def make_region(*arg, **kw):
+ """Instantiate a new :class:`.CacheRegion`.
+
+ Currently, :func:`.make_region` is a passthrough
+ to :class:`.CacheRegion`. See that class for
+ constructor arguments.
+
+ """
+ return CacheRegion(*arg, **kw)
« no previous file with comments | « third_party/google-endpoints/dogpile/cache/proxy.py ('k') | third_party/google-endpoints/dogpile/cache/util.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698