| 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)
|
|
|