Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/dogpile/cache/region.py : 46%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from __future__ import with_statement
3import contextlib
4import datetime
5from functools import partial
6from functools import wraps
7import logging
8from numbers import Number
9import threading
10import time
12from decorator import decorate
14from . import exception
15from .api import CachedValue
16from .api import NO_VALUE
17from .backends import _backend_loader
18from .backends import register_backend # noqa
19from .proxy import ProxyBackend
20from .util import function_key_generator
21from .util import function_multi_key_generator
22from .util import repr_obj
23from .. import Lock
24from .. import NeedRegenerationException
25from ..util import coerce_string_conf
26from ..util import compat
27from ..util import memoized_property
28from ..util import NameRegistry
29from ..util import PluginLoader
31value_version = 1
32"""An integer placed in the :class:`.CachedValue`
33so that new versions of dogpile.cache can detect cached
34values from a previous, backwards-incompatible version.
36"""
38log = logging.getLogger(__name__)
41class RegionInvalidationStrategy(object):
42 """Region invalidation strategy interface
44 Implement this interface and pass implementation instance
45 to :meth:`.CacheRegion.configure` to override default region invalidation.
47 Example::
49 class CustomInvalidationStrategy(RegionInvalidationStrategy):
51 def __init__(self):
52 self._soft_invalidated = None
53 self._hard_invalidated = None
55 def invalidate(self, hard=None):
56 if hard:
57 self._soft_invalidated = None
58 self._hard_invalidated = time.time()
59 else:
60 self._soft_invalidated = time.time()
61 self._hard_invalidated = None
63 def is_invalidated(self, timestamp):
64 return ((self._soft_invalidated and
65 timestamp < self._soft_invalidated) or
66 (self._hard_invalidated and
67 timestamp < self._hard_invalidated))
69 def was_hard_invalidated(self):
70 return bool(self._hard_invalidated)
72 def is_hard_invalidated(self, timestamp):
73 return (self._hard_invalidated and
74 timestamp < self._hard_invalidated)
76 def was_soft_invalidated(self):
77 return bool(self._soft_invalidated)
79 def is_soft_invalidated(self, timestamp):
80 return (self._soft_invalidated and
81 timestamp < self._soft_invalidated)
83 The custom implementation is injected into a :class:`.CacheRegion`
84 at configure time using the
85 :paramref:`.CacheRegion.configure.region_invalidator` parameter::
87 region = CacheRegion()
89 region = region.configure(region_invalidator=CustomInvalidationStrategy()) # noqa
91 Invalidation strategies that wish to have access to the
92 :class:`.CacheRegion` itself should construct the invalidator given the
93 region as an argument::
95 class MyInvalidator(RegionInvalidationStrategy):
96 def __init__(self, region):
97 self.region = region
98 # ...
100 # ...
102 region = CacheRegion()
103 region = region.configure(region_invalidator=MyInvalidator(region))
105 .. versionadded:: 0.6.2
107 .. seealso::
109 :paramref:`.CacheRegion.configure.region_invalidator`
111 """
113 def invalidate(self, hard=True):
114 """Region invalidation.
116 :class:`.CacheRegion` propagated call.
117 The default invalidation system works by setting
118 a current timestamp (using ``time.time()``) to consider all older
119 timestamps effectively invalidated.
121 """
123 raise NotImplementedError()
125 def is_hard_invalidated(self, timestamp):
126 """Check timestamp to determine if it was hard invalidated.
128 :return: Boolean. True if ``timestamp`` is older than
129 the last region invalidation time and region is invalidated
130 in hard mode.
132 """
134 raise NotImplementedError()
136 def is_soft_invalidated(self, timestamp):
137 """Check timestamp to determine if it was soft invalidated.
139 :return: Boolean. True if ``timestamp`` is older than
140 the last region invalidation time and region is invalidated
141 in soft mode.
143 """
145 raise NotImplementedError()
147 def is_invalidated(self, timestamp):
148 """Check timestamp to determine if it was invalidated.
150 :return: Boolean. True if ``timestamp`` is older than
151 the last region invalidation time.
153 """
155 raise NotImplementedError()
157 def was_soft_invalidated(self):
158 """Indicate the region was invalidated in soft mode.
160 :return: Boolean. True if region was invalidated in soft mode.
162 """
164 raise NotImplementedError()
166 def was_hard_invalidated(self):
167 """Indicate the region was invalidated in hard mode.
169 :return: Boolean. True if region was invalidated in hard mode.
171 """
173 raise NotImplementedError()
176class DefaultInvalidationStrategy(RegionInvalidationStrategy):
177 def __init__(self):
178 self._is_hard_invalidated = None
179 self._invalidated = None
181 def invalidate(self, hard=True):
182 self._is_hard_invalidated = bool(hard)
183 self._invalidated = time.time()
185 def is_invalidated(self, timestamp):
186 return self._invalidated is not None and timestamp < self._invalidated
188 def was_hard_invalidated(self):
189 return self._is_hard_invalidated is True
191 def is_hard_invalidated(self, timestamp):
192 return self.was_hard_invalidated() and self.is_invalidated(timestamp)
194 def was_soft_invalidated(self):
195 return self._is_hard_invalidated is False
197 def is_soft_invalidated(self, timestamp):
198 return self.was_soft_invalidated() and self.is_invalidated(timestamp)
201class CacheRegion(object):
202 r"""A front end to a particular cache backend.
204 :param name: Optional, a string name for the region.
205 This isn't used internally
206 but can be accessed via the ``.name`` parameter, helpful
207 for configuring a region from a config file.
208 :param function_key_generator: Optional. A
209 function that will produce a "cache key" given
210 a data creation function and arguments, when using
211 the :meth:`.CacheRegion.cache_on_arguments` method.
212 The structure of this function
213 should be two levels: given the data creation function,
214 return a new function that generates the key based on
215 the given arguments. Such as::
217 def my_key_generator(namespace, fn, **kw):
218 fname = fn.__name__
219 def generate_key(*arg):
220 return namespace + "_" + fname + "_".join(str(s) for s in arg)
221 return generate_key
224 region = make_region(
225 function_key_generator = my_key_generator
226 ).configure(
227 "dogpile.cache.dbm",
228 expiration_time=300,
229 arguments={
230 "filename":"file.dbm"
231 }
232 )
234 The ``namespace`` is that passed to
235 :meth:`.CacheRegion.cache_on_arguments`. It's not consulted
236 outside this function, so in fact can be of any form.
237 For example, it can be passed as a tuple, used to specify
238 arguments to pluck from \**kw::
240 def my_key_generator(namespace, fn):
241 def generate_key(*arg, **kw):
242 return ":".join(
243 [kw[k] for k in namespace] +
244 [str(x) for x in arg]
245 )
246 return generate_key
249 Where the decorator might be used as::
251 @my_region.cache_on_arguments(namespace=('x', 'y'))
252 def my_function(a, b, **kw):
253 return my_data()
255 .. seealso::
257 :func:`.function_key_generator` - default key generator
259 :func:`.kwarg_function_key_generator` - optional gen that also
260 uses keyword arguments
262 :param function_multi_key_generator: Optional.
263 Similar to ``function_key_generator`` parameter, but it's used in
264 :meth:`.CacheRegion.cache_multi_on_arguments`. Generated function
265 should return list of keys. For example::
267 def my_multi_key_generator(namespace, fn, **kw):
268 namespace = fn.__name__ + (namespace or '')
270 def generate_keys(*args):
271 return [namespace + ':' + str(a) for a in args]
273 return generate_keys
275 :param key_mangler: Function which will be used on all incoming
276 keys before passing to the backend. Defaults to ``None``,
277 in which case the key mangling function recommended by
278 the cache backend will be used. A typical mangler
279 is the SHA1 mangler found at :func:`.sha1_mangle_key`
280 which coerces keys into a SHA1
281 hash, so that the string length is fixed. To
282 disable all key mangling, set to ``False``. Another typical
283 mangler is the built-in Python function ``str``, which can be used
284 to convert non-string or Unicode keys to bytestrings, which is
285 needed when using a backend such as bsddb or dbm under Python 2.x
286 in conjunction with Unicode keys.
287 :param async_creation_runner: A callable that, when specified,
288 will be passed to and called by dogpile.lock when
289 there is a stale value present in the cache. It will be passed the
290 mutex and is responsible releasing that mutex when finished.
291 This can be used to defer the computation of expensive creator
292 functions to later points in the future by way of, for example, a
293 background thread, a long-running queue, or a task manager system
294 like Celery.
296 For a specific example using async_creation_runner, new values can
297 be created in a background thread like so::
299 import threading
301 def async_creation_runner(cache, somekey, creator, mutex):
302 ''' Used by dogpile.core:Lock when appropriate '''
303 def runner():
304 try:
305 value = creator()
306 cache.set(somekey, value)
307 finally:
308 mutex.release()
310 thread = threading.Thread(target=runner)
311 thread.start()
314 region = make_region(
315 async_creation_runner=async_creation_runner,
316 ).configure(
317 'dogpile.cache.memcached',
318 expiration_time=5,
319 arguments={
320 'url': '127.0.0.1:11211',
321 'distributed_lock': True,
322 }
323 )
325 Remember that the first request for a key with no associated
326 value will always block; async_creator will not be invoked.
327 However, subsequent requests for cached-but-expired values will
328 still return promptly. They will be refreshed by whatever
329 asynchronous means the provided async_creation_runner callable
330 implements.
332 By default the async_creation_runner is disabled and is set
333 to ``None``.
335 .. versionadded:: 0.4.2 added the async_creation_runner
336 feature.
338 """
340 def __init__(
341 self,
342 name=None,
343 function_key_generator=function_key_generator,
344 function_multi_key_generator=function_multi_key_generator,
345 key_mangler=None,
346 async_creation_runner=None,
347 ):
348 """Construct a new :class:`.CacheRegion`."""
349 self.name = name
350 self.function_key_generator = function_key_generator
351 self.function_multi_key_generator = function_multi_key_generator
352 self.key_mangler = self._user_defined_key_mangler = key_mangler
353 self.async_creation_runner = async_creation_runner
354 self.region_invalidator = DefaultInvalidationStrategy()
356 def configure(
357 self,
358 backend,
359 expiration_time=None,
360 arguments=None,
361 _config_argument_dict=None,
362 _config_prefix=None,
363 wrap=None,
364 replace_existing_backend=False,
365 region_invalidator=None,
366 ):
367 """Configure a :class:`.CacheRegion`.
369 The :class:`.CacheRegion` itself
370 is returned.
372 :param backend: Required. This is the name of the
373 :class:`.CacheBackend` to use, and is resolved by loading
374 the class from the ``dogpile.cache`` entrypoint.
376 :param expiration_time: Optional. The expiration time passed
377 to the dogpile system. May be passed as an integer number
378 of seconds, or as a ``datetime.timedelta`` value.
380 .. versionadded 0.5.0
381 ``expiration_time`` may be optionally passed as a
382 ``datetime.timedelta`` value.
384 The :meth:`.CacheRegion.get_or_create`
385 method as well as the :meth:`.CacheRegion.cache_on_arguments`
386 decorator (though note: **not** the :meth:`.CacheRegion.get`
387 method) will call upon the value creation function after this
388 time period has passed since the last generation.
390 :param arguments: Optional. The structure here is passed
391 directly to the constructor of the :class:`.CacheBackend`
392 in use, though is typically a dictionary.
394 :param wrap: Optional. A list of :class:`.ProxyBackend`
395 classes and/or instances, each of which will be applied
396 in a chain to ultimately wrap the original backend,
397 so that custom functionality augmentation can be applied.
399 .. versionadded:: 0.5.0
401 .. seealso::
403 :ref:`changing_backend_behavior`
405 :param replace_existing_backend: if True, the existing cache backend
406 will be replaced. Without this flag, an exception is raised if
407 a backend is already configured.
409 .. versionadded:: 0.5.7
411 :param region_invalidator: Optional. Override default invalidation
412 strategy with custom implementation of
413 :class:`.RegionInvalidationStrategy`.
415 .. versionadded:: 0.6.2
417 """
419 if "backend" in self.__dict__ and not replace_existing_backend:
420 raise exception.RegionAlreadyConfigured(
421 "This region is already "
422 "configured with backend: %s. "
423 "Specify replace_existing_backend=True to replace."
424 % self.backend
425 )
427 try:
428 backend_cls = _backend_loader.load(backend)
429 except PluginLoader.NotFound:
430 raise exception.PluginNotFound(
431 "Couldn't find cache plugin to load: %s" % backend
432 )
434 if _config_argument_dict:
435 self.backend = backend_cls.from_config_dict(
436 _config_argument_dict, _config_prefix
437 )
438 else:
439 self.backend = backend_cls(arguments or {})
441 if not expiration_time or isinstance(expiration_time, Number):
442 self.expiration_time = expiration_time
443 elif isinstance(expiration_time, datetime.timedelta):
444 self.expiration_time = int(
445 compat.timedelta_total_seconds(expiration_time)
446 )
447 else:
448 raise exception.ValidationError(
449 "expiration_time is not a number or timedelta."
450 )
452 if not self._user_defined_key_mangler:
453 self.key_mangler = self.backend.key_mangler
455 self._lock_registry = NameRegistry(self._create_mutex)
457 if getattr(wrap, "__iter__", False):
458 for wrapper in reversed(wrap):
459 self.wrap(wrapper)
461 if region_invalidator:
462 self.region_invalidator = region_invalidator
464 return self
466 def wrap(self, proxy):
467 """ Takes a ProxyBackend instance or class and wraps the
468 attached backend. """
470 # if we were passed a type rather than an instance then
471 # initialize it.
472 if type(proxy) == type:
473 proxy = proxy()
475 if not issubclass(type(proxy), ProxyBackend):
476 raise TypeError(
477 "Type %s is not a valid ProxyBackend" % type(proxy)
478 )
480 self.backend = proxy.wrap(self.backend)
482 def _mutex(self, key):
483 return self._lock_registry.get(key)
485 class _LockWrapper(object):
486 """weakref-capable wrapper for threading.Lock"""
488 def __init__(self):
489 self.lock = threading.Lock()
491 def acquire(self, wait=True):
492 return self.lock.acquire(wait)
494 def release(self):
495 self.lock.release()
497 def _create_mutex(self, key):
498 mutex = self.backend.get_mutex(key)
499 if mutex is not None:
500 return mutex
501 else:
502 return self._LockWrapper()
504 # cached value
505 _actual_backend = None
507 @property
508 def actual_backend(self):
509 """Return the ultimate backend underneath any proxies.
511 The backend might be the result of one or more ``proxy.wrap``
512 applications. If so, derive the actual underlying backend.
514 .. versionadded:: 0.6.6
516 """
517 if self._actual_backend is None:
518 _backend = self.backend
519 while hasattr(_backend, "proxied"):
520 _backend = _backend.proxied
521 self._actual_backend = _backend
522 return self._actual_backend
524 def invalidate(self, hard=True):
525 """Invalidate this :class:`.CacheRegion`.
527 The default invalidation system works by setting
528 a current timestamp (using ``time.time()``)
529 representing the "minimum creation time" for
530 a value. Any retrieved value whose creation
531 time is prior to this timestamp
532 is considered to be stale. It does not
533 affect the data in the cache in any way, and is
534 **local to this instance of :class:`.CacheRegion`.**
536 .. warning::
538 The :meth:`.CacheRegion.invalidate` method's default mode of
539 operation is to set a timestamp **local to this CacheRegion
540 in this Python process only**. It does not impact other Python
541 processes or regions as the timestamp is **only stored locally in
542 memory**. To implement invalidation where the
543 timestamp is stored in the cache or similar so that all Python
544 processes can be affected by an invalidation timestamp, implement a
545 custom :class:`.RegionInvalidationStrategy`.
547 Once set, the invalidation time is honored by
548 the :meth:`.CacheRegion.get_or_create`,
549 :meth:`.CacheRegion.get_or_create_multi` and
550 :meth:`.CacheRegion.get` methods.
552 The method supports both "hard" and "soft" invalidation
553 options. With "hard" invalidation,
554 :meth:`.CacheRegion.get_or_create` will force an immediate
555 regeneration of the value which all getters will wait for.
556 With "soft" invalidation, subsequent getters will return the
557 "old" value until the new one is available.
559 Usage of "soft" invalidation requires that the region or the method
560 is given a non-None expiration time.
562 .. versionadded:: 0.3.0
564 :param hard: if True, cache values will all require immediate
565 regeneration; dogpile logic won't be used. If False, the
566 creation time of existing values will be pushed back before
567 the expiration time so that a return+regen will be invoked.
569 .. versionadded:: 0.5.1
571 """
572 self.region_invalidator.invalidate(hard)
574 def configure_from_config(self, config_dict, prefix):
575 """Configure from a configuration dictionary
576 and a prefix.
578 Example::
580 local_region = make_region()
581 memcached_region = make_region()
583 # regions are ready to use for function
584 # decorators, but not yet for actual caching
586 # later, when config is available
587 myconfig = {
588 "cache.local.backend":"dogpile.cache.dbm",
589 "cache.local.arguments.filename":"/path/to/dbmfile.dbm",
590 "cache.memcached.backend":"dogpile.cache.pylibmc",
591 "cache.memcached.arguments.url":"127.0.0.1, 10.0.0.1",
592 }
593 local_region.configure_from_config(myconfig, "cache.local.")
594 memcached_region.configure_from_config(myconfig,
595 "cache.memcached.")
597 """
598 config_dict = coerce_string_conf(config_dict)
599 return self.configure(
600 config_dict["%sbackend" % prefix],
601 expiration_time=config_dict.get(
602 "%sexpiration_time" % prefix, None
603 ),
604 _config_argument_dict=config_dict,
605 _config_prefix="%sarguments." % prefix,
606 wrap=config_dict.get("%swrap" % prefix, None),
607 replace_existing_backend=config_dict.get(
608 "%sreplace_existing_backend" % prefix, False
609 ),
610 )
612 @memoized_property
613 def backend(self):
614 raise exception.RegionNotConfigured(
615 "No backend is configured on this region."
616 )
618 @property
619 def is_configured(self):
620 """Return True if the backend has been configured via the
621 :meth:`.CacheRegion.configure` method already.
623 .. versionadded:: 0.5.1
625 """
626 return "backend" in self.__dict__
628 def get(self, key, expiration_time=None, ignore_expiration=False):
629 """Return a value from the cache, based on the given key.
631 If the value is not present, the method returns the token
632 ``NO_VALUE``. ``NO_VALUE`` evaluates to False, but is separate from
633 ``None`` to distinguish between a cached value of ``None``.
635 By default, the configured expiration time of the
636 :class:`.CacheRegion`, or alternatively the expiration
637 time supplied by the ``expiration_time`` argument,
638 is tested against the creation time of the retrieved
639 value versus the current time (as reported by ``time.time()``).
640 If stale, the cached value is ignored and the ``NO_VALUE``
641 token is returned. Passing the flag ``ignore_expiration=True``
642 bypasses the expiration time check.
644 .. versionchanged:: 0.3.0
645 :meth:`.CacheRegion.get` now checks the value's creation time
646 against the expiration time, rather than returning
647 the value unconditionally.
649 The method also interprets the cached value in terms
650 of the current "invalidation" time as set by
651 the :meth:`.invalidate` method. If a value is present,
652 but its creation time is older than the current
653 invalidation time, the ``NO_VALUE`` token is returned.
654 Passing the flag ``ignore_expiration=True`` bypasses
655 the invalidation time check.
657 .. versionadded:: 0.3.0
658 Support for the :meth:`.CacheRegion.invalidate`
659 method.
661 :param key: Key to be retrieved. While it's typical for a key to be a
662 string, it is ultimately passed directly down to the cache backend,
663 before being optionally processed by the key_mangler function, so can
664 be of any type recognized by the backend or by the key_mangler
665 function, if present.
667 :param expiration_time: Optional expiration time value
668 which will supersede that configured on the :class:`.CacheRegion`
669 itself.
671 .. note:: The :paramref:`.CacheRegion.get.expiration_time`
672 argument is **not persisted in the cache** and is relevant
673 only to **this specific cache retrieval operation**, relative to
674 the creation time stored with the existing cached value.
675 Subsequent calls to :meth:`.CacheRegion.get` are **not** affected
676 by this value.
678 .. versionadded:: 0.3.0
680 :param ignore_expiration: if ``True``, the value is returned
681 from the cache if present, regardless of configured
682 expiration times or whether or not :meth:`.invalidate`
683 was called.
685 .. versionadded:: 0.3.0
687 .. seealso::
689 :meth:`.CacheRegion.get_multi`
691 :meth:`.CacheRegion.get_or_create`
693 :meth:`.CacheRegion.set`
695 :meth:`.CacheRegion.delete`
698 """
700 if self.key_mangler:
701 key = self.key_mangler(key)
702 value = self.backend.get(key)
703 value = self._unexpired_value_fn(expiration_time, ignore_expiration)(
704 value
705 )
707 return value.payload
709 def _unexpired_value_fn(self, expiration_time, ignore_expiration):
710 if ignore_expiration:
711 return lambda value: value
712 else:
713 if expiration_time is None:
714 expiration_time = self.expiration_time
716 current_time = time.time()
718 def value_fn(value):
719 if value is NO_VALUE:
720 return value
721 elif (
722 expiration_time is not None
723 and current_time - value.metadata["ct"] > expiration_time
724 ):
725 return NO_VALUE
726 elif self.region_invalidator.is_invalidated(
727 value.metadata["ct"]
728 ):
729 return NO_VALUE
730 else:
731 return value
733 return value_fn
735 def get_multi(self, keys, expiration_time=None, ignore_expiration=False):
736 """Return multiple values from the cache, based on the given keys.
738 Returns values as a list matching the keys given.
740 E.g.::
742 values = region.get_multi(["one", "two", "three"])
744 To convert values to a dictionary, use ``zip()``::
746 keys = ["one", "two", "three"]
747 values = region.get_multi(keys)
748 dictionary = dict(zip(keys, values))
750 Keys which aren't present in the list are returned as
751 the ``NO_VALUE`` token. ``NO_VALUE`` evaluates to False,
752 but is separate from
753 ``None`` to distinguish between a cached value of ``None``.
755 By default, the configured expiration time of the
756 :class:`.CacheRegion`, or alternatively the expiration
757 time supplied by the ``expiration_time`` argument,
758 is tested against the creation time of the retrieved
759 value versus the current time (as reported by ``time.time()``).
760 If stale, the cached value is ignored and the ``NO_VALUE``
761 token is returned. Passing the flag ``ignore_expiration=True``
762 bypasses the expiration time check.
764 .. versionadded:: 0.5.0
766 """
767 if not keys:
768 return []
770 if self.key_mangler:
771 keys = list(map(lambda key: self.key_mangler(key), keys))
773 backend_values = self.backend.get_multi(keys)
775 _unexpired_value_fn = self._unexpired_value_fn(
776 expiration_time, ignore_expiration
777 )
778 return [
779 value.payload if value is not NO_VALUE else value
780 for value in (
781 _unexpired_value_fn(value) for value in backend_values
782 )
783 ]
785 @contextlib.contextmanager
786 def _log_time(self, keys):
787 start_time = time.time()
788 yield
789 seconds = time.time() - start_time
790 log.debug(
791 "Cache value generated in %(seconds).3f seconds for key(s): "
792 "%(keys)r",
793 {"seconds": seconds, "keys": repr_obj(keys)},
794 )
796 def _is_cache_miss(self, value, orig_key):
797 if value is NO_VALUE:
798 log.debug("No value present for key: %r", orig_key)
799 elif value.metadata["v"] != value_version:
800 log.debug("Dogpile version update for key: %r", orig_key)
801 elif self.region_invalidator.is_hard_invalidated(value.metadata["ct"]):
802 log.debug("Hard invalidation detected for key: %r", orig_key)
803 else:
804 return False
806 return True
808 def get_or_create(
809 self,
810 key,
811 creator,
812 expiration_time=None,
813 should_cache_fn=None,
814 creator_args=None,
815 ):
816 """Return a cached value based on the given key.
818 If the value does not exist or is considered to be expired
819 based on its creation time, the given
820 creation function may or may not be used to recreate the value
821 and persist the newly generated value in the cache.
823 Whether or not the function is used depends on if the
824 *dogpile lock* can be acquired or not. If it can't, it means
825 a different thread or process is already running a creation
826 function for this key against the cache. When the dogpile
827 lock cannot be acquired, the method will block if no
828 previous value is available, until the lock is released and
829 a new value available. If a previous value
830 is available, that value is returned immediately without blocking.
832 If the :meth:`.invalidate` method has been called, and
833 the retrieved value's timestamp is older than the invalidation
834 timestamp, the value is unconditionally prevented from
835 being returned. The method will attempt to acquire the dogpile
836 lock to generate a new value, or will wait
837 until the lock is released to return the new value.
839 .. versionchanged:: 0.3.0
840 The value is unconditionally regenerated if the creation
841 time is older than the last call to :meth:`.invalidate`.
843 :param key: Key to be retrieved. While it's typical for a key to be a
844 string, it is ultimately passed directly down to the cache backend,
845 before being optionally processed by the key_mangler function, so can
846 be of any type recognized by the backend or by the key_mangler
847 function, if present.
849 :param creator: function which creates a new value.
851 :param creator_args: optional tuple of (args, kwargs) that will be
852 passed to the creator function if present.
854 .. versionadded:: 0.7.0
856 :param expiration_time: optional expiration time which will override
857 the expiration time already configured on this :class:`.CacheRegion`
858 if not None. To set no expiration, use the value -1.
860 .. note:: The :paramref:`.CacheRegion.get_or_create.expiration_time`
861 argument is **not persisted in the cache** and is relevant
862 only to **this specific cache retrieval operation**, relative to
863 the creation time stored with the existing cached value.
864 Subsequent calls to :meth:`.CacheRegion.get_or_create` are **not**
865 affected by this value.
867 :param should_cache_fn: optional callable function which will receive
868 the value returned by the "creator", and will then return True or
869 False, indicating if the value should actually be cached or not. If
870 it returns False, the value is still returned, but isn't cached.
871 E.g.::
873 def dont_cache_none(value):
874 return value is not None
876 value = region.get_or_create("some key",
877 create_value,
878 should_cache_fn=dont_cache_none)
880 Above, the function returns the value of create_value() if
881 the cache is invalid, however if the return value is None,
882 it won't be cached.
884 .. versionadded:: 0.4.3
886 .. seealso::
888 :meth:`.CacheRegion.get`
890 :meth:`.CacheRegion.cache_on_arguments` - applies
891 :meth:`.get_or_create` to any function using a decorator.
893 :meth:`.CacheRegion.get_or_create_multi` - multiple key/value
894 version
896 """
897 orig_key = key
898 if self.key_mangler:
899 key = self.key_mangler(key)
901 def get_value():
902 value = self.backend.get(key)
903 if self._is_cache_miss(value, orig_key):
904 raise NeedRegenerationException()
906 ct = value.metadata["ct"]
907 if self.region_invalidator.is_soft_invalidated(ct):
908 ct = time.time() - expiration_time - 0.0001
910 return value.payload, ct
912 def gen_value():
913 with self._log_time(orig_key):
914 if creator_args:
915 created_value = creator(
916 *creator_args[0], **creator_args[1]
917 )
918 else:
919 created_value = creator()
920 value = self._value(created_value)
922 if not should_cache_fn or should_cache_fn(created_value):
923 self.backend.set(key, value)
925 return value.payload, value.metadata["ct"]
927 if expiration_time is None:
928 expiration_time = self.expiration_time
930 if (
931 expiration_time is None
932 and self.region_invalidator.was_soft_invalidated()
933 ):
934 raise exception.DogpileCacheException(
935 "Non-None expiration time required " "for soft invalidation"
936 )
938 if expiration_time == -1:
939 expiration_time = None
941 if self.async_creation_runner:
943 def async_creator(mutex):
944 if creator_args:
946 @wraps(creator)
947 def go():
948 return creator(*creator_args[0], **creator_args[1])
950 else:
951 go = creator
952 return self.async_creation_runner(self, orig_key, go, mutex)
954 else:
955 async_creator = None
957 with Lock(
958 self._mutex(key),
959 gen_value,
960 get_value,
961 expiration_time,
962 async_creator,
963 ) as value:
964 return value
966 def get_or_create_multi(
967 self, keys, creator, expiration_time=None, should_cache_fn=None
968 ):
969 """Return a sequence of cached values based on a sequence of keys.
971 The behavior for generation of values based on keys corresponds
972 to that of :meth:`.Region.get_or_create`, with the exception that
973 the ``creator()`` function may be asked to generate any subset of
974 the given keys. The list of keys to be generated is passed to
975 ``creator()``, and ``creator()`` should return the generated values
976 as a sequence corresponding to the order of the keys.
978 The method uses the same approach as :meth:`.Region.get_multi`
979 and :meth:`.Region.set_multi` to get and set values from the
980 backend.
982 If you are using a :class:`.CacheBackend` or :class:`.ProxyBackend`
983 that modifies values, take note this function invokes
984 ``.set_multi()`` for newly generated values using the same values it
985 returns to the calling function. A correct implementation of
986 ``.set_multi()`` will not modify values in-place on the submitted
987 ``mapping`` dict.
989 :param keys: Sequence of keys to be retrieved.
991 :param creator: function which accepts a sequence of keys and
992 returns a sequence of new values.
994 :param expiration_time: optional expiration time which will override
995 the expiration time already configured on this :class:`.CacheRegion`
996 if not None. To set no expiration, use the value -1.
998 :param should_cache_fn: optional callable function which will receive
999 each value returned by the "creator", and will then return True or
1000 False, indicating if the value should actually be cached or not. If
1001 it returns False, the value is still returned, but isn't cached.
1003 .. versionadded:: 0.5.0
1005 .. seealso::
1008 :meth:`.CacheRegion.cache_multi_on_arguments`
1010 :meth:`.CacheRegion.get_or_create`
1012 """
1014 def get_value(key):
1015 value = values.get(key, NO_VALUE)
1017 if self._is_cache_miss(value, orig_key):
1018 # dogpile.core understands a 0 here as
1019 # "the value is not available", e.g.
1020 # _has_value() will return False.
1021 return value.payload, 0
1022 else:
1023 ct = value.metadata["ct"]
1024 if self.region_invalidator.is_soft_invalidated(ct):
1025 ct = time.time() - expiration_time - 0.0001
1027 return value.payload, ct
1029 def gen_value():
1030 raise NotImplementedError()
1032 def async_creator(key, mutex):
1033 mutexes[key] = mutex
1035 if expiration_time is None:
1036 expiration_time = self.expiration_time
1038 if (
1039 expiration_time is None
1040 and self.region_invalidator.was_soft_invalidated()
1041 ):
1042 raise exception.DogpileCacheException(
1043 "Non-None expiration time required " "for soft invalidation"
1044 )
1046 if expiration_time == -1:
1047 expiration_time = None
1049 mutexes = {}
1051 sorted_unique_keys = sorted(set(keys))
1053 if self.key_mangler:
1054 mangled_keys = [self.key_mangler(k) for k in sorted_unique_keys]
1055 else:
1056 mangled_keys = sorted_unique_keys
1058 orig_to_mangled = dict(zip(sorted_unique_keys, mangled_keys))
1060 values = dict(zip(mangled_keys, self.backend.get_multi(mangled_keys)))
1062 for orig_key, mangled_key in orig_to_mangled.items():
1063 with Lock(
1064 self._mutex(mangled_key),
1065 gen_value,
1066 lambda: get_value(mangled_key),
1067 expiration_time,
1068 async_creator=lambda mutex: async_creator(orig_key, mutex),
1069 ):
1070 pass
1071 try:
1072 if mutexes:
1073 # sort the keys, the idea is to prevent deadlocks.
1074 # though haven't been able to simulate one anyway.
1075 keys_to_get = sorted(mutexes)
1077 with self._log_time(keys_to_get):
1078 new_values = creator(*keys_to_get)
1080 values_w_created = dict(
1081 (orig_to_mangled[k], self._value(v))
1082 for k, v in zip(keys_to_get, new_values)
1083 )
1085 if not should_cache_fn:
1086 self.backend.set_multi(values_w_created)
1087 else:
1088 values_to_cache = dict(
1089 (k, v)
1090 for k, v in values_w_created.items()
1091 if should_cache_fn(v[0])
1092 )
1094 if values_to_cache:
1095 self.backend.set_multi(values_to_cache)
1097 values.update(values_w_created)
1098 return [values[orig_to_mangled[k]].payload for k in keys]
1099 finally:
1100 for mutex in mutexes.values():
1101 mutex.release()
1103 def _value(self, value):
1104 """Return a :class:`.CachedValue` given a value."""
1105 return CachedValue(value, {"ct": time.time(), "v": value_version})
1107 def set(self, key, value):
1108 """Place a new value in the cache under the given key."""
1110 if self.key_mangler:
1111 key = self.key_mangler(key)
1112 self.backend.set(key, self._value(value))
1114 def set_multi(self, mapping):
1115 """Place new values in the cache under the given keys.
1117 .. versionadded:: 0.5.0
1119 """
1120 if not mapping:
1121 return
1123 if self.key_mangler:
1124 mapping = dict(
1125 (self.key_mangler(k), self._value(v))
1126 for k, v in mapping.items()
1127 )
1128 else:
1129 mapping = dict((k, self._value(v)) for k, v in mapping.items())
1130 self.backend.set_multi(mapping)
1132 def delete(self, key):
1133 """Remove a value from the cache.
1135 This operation is idempotent (can be called multiple times, or on a
1136 non-existent key, safely)
1137 """
1139 if self.key_mangler:
1140 key = self.key_mangler(key)
1142 self.backend.delete(key)
1144 def delete_multi(self, keys):
1145 """Remove multiple values from the cache.
1147 This operation is idempotent (can be called multiple times, or on a
1148 non-existent key, safely)
1150 .. versionadded:: 0.5.0
1152 """
1154 if self.key_mangler:
1155 keys = list(map(lambda key: self.key_mangler(key), keys))
1157 self.backend.delete_multi(keys)
1159 def cache_on_arguments(
1160 self,
1161 namespace=None,
1162 expiration_time=None,
1163 should_cache_fn=None,
1164 to_str=compat.string_type,
1165 function_key_generator=None,
1166 ):
1167 """A function decorator that will cache the return
1168 value of the function using a key derived from the
1169 function itself and its arguments.
1171 The decorator internally makes use of the
1172 :meth:`.CacheRegion.get_or_create` method to access the
1173 cache and conditionally call the function. See that
1174 method for additional behavioral details.
1176 E.g.::
1178 @someregion.cache_on_arguments()
1179 def generate_something(x, y):
1180 return somedatabase.query(x, y)
1182 The decorated function can then be called normally, where
1183 data will be pulled from the cache region unless a new
1184 value is needed::
1186 result = generate_something(5, 6)
1188 The function is also given an attribute ``invalidate()``, which
1189 provides for invalidation of the value. Pass to ``invalidate()``
1190 the same arguments you'd pass to the function itself to represent
1191 a particular value::
1193 generate_something.invalidate(5, 6)
1195 Another attribute ``set()`` is added to provide extra caching
1196 possibilities relative to the function. This is a convenience
1197 method for :meth:`.CacheRegion.set` which will store a given
1198 value directly without calling the decorated function.
1199 The value to be cached is passed as the first argument, and the
1200 arguments which would normally be passed to the function
1201 should follow::
1203 generate_something.set(3, 5, 6)
1205 The above example is equivalent to calling
1206 ``generate_something(5, 6)``, if the function were to produce
1207 the value ``3`` as the value to be cached.
1209 .. versionadded:: 0.4.1 Added ``set()`` method to decorated function.
1211 Similar to ``set()`` is ``refresh()``. This attribute will
1212 invoke the decorated function and populate a new value into
1213 the cache with the new value, as well as returning that value::
1215 newvalue = generate_something.refresh(5, 6)
1217 .. versionadded:: 0.5.0 Added ``refresh()`` method to decorated
1218 function.
1220 ``original()`` on other hand will invoke the decorated function
1221 without any caching::
1223 newvalue = generate_something.original(5, 6)
1225 .. versionadded:: 0.6.0 Added ``original()`` method to decorated
1226 function.
1228 Lastly, the ``get()`` method returns either the value cached
1229 for the given key, or the token ``NO_VALUE`` if no such key
1230 exists::
1232 value = generate_something.get(5, 6)
1234 .. versionadded:: 0.5.3 Added ``get()`` method to decorated
1235 function.
1237 The default key generation will use the name
1238 of the function, the module name for the function,
1239 the arguments passed, as well as an optional "namespace"
1240 parameter in order to generate a cache key.
1242 Given a function ``one`` inside the module
1243 ``myapp.tools``::
1245 @region.cache_on_arguments(namespace="foo")
1246 def one(a, b):
1247 return a + b
1249 Above, calling ``one(3, 4)`` will produce a
1250 cache key as follows::
1252 myapp.tools:one|foo|3 4
1254 The key generator will ignore an initial argument
1255 of ``self`` or ``cls``, making the decorator suitable
1256 (with caveats) for use with instance or class methods.
1257 Given the example::
1259 class MyClass(object):
1260 @region.cache_on_arguments(namespace="foo")
1261 def one(self, a, b):
1262 return a + b
1264 The cache key above for ``MyClass().one(3, 4)`` will
1265 again produce the same cache key of ``myapp.tools:one|foo|3 4`` -
1266 the name ``self`` is skipped.
1268 The ``namespace`` parameter is optional, and is used
1269 normally to disambiguate two functions of the same
1270 name within the same module, as can occur when decorating
1271 instance or class methods as below::
1273 class MyClass(object):
1274 @region.cache_on_arguments(namespace='MC')
1275 def somemethod(self, x, y):
1276 ""
1278 class MyOtherClass(object):
1279 @region.cache_on_arguments(namespace='MOC')
1280 def somemethod(self, x, y):
1281 ""
1283 Above, the ``namespace`` parameter disambiguates
1284 between ``somemethod`` on ``MyClass`` and ``MyOtherClass``.
1285 Python class declaration mechanics otherwise prevent
1286 the decorator from having awareness of the ``MyClass``
1287 and ``MyOtherClass`` names, as the function is received
1288 by the decorator before it becomes an instance method.
1290 The function key generation can be entirely replaced
1291 on a per-region basis using the ``function_key_generator``
1292 argument present on :func:`.make_region` and
1293 :class:`.CacheRegion`. If defaults to
1294 :func:`.function_key_generator`.
1296 :param namespace: optional string argument which will be
1297 established as part of the cache key. This may be needed
1298 to disambiguate functions of the same name within the same
1299 source file, such as those
1300 associated with classes - note that the decorator itself
1301 can't see the parent class on a function as the class is
1302 being declared.
1304 :param expiration_time: if not None, will override the normal
1305 expiration time.
1307 May be specified as a callable, taking no arguments, that
1308 returns a value to be used as the ``expiration_time``. This callable
1309 will be called whenever the decorated function itself is called, in
1310 caching or retrieving. Thus, this can be used to
1311 determine a *dynamic* expiration time for the cached function
1312 result. Example use cases include "cache the result until the
1313 end of the day, week or time period" and "cache until a certain date
1314 or time passes".
1316 .. versionchanged:: 0.5.0
1317 ``expiration_time`` may be passed as a callable to
1318 :meth:`.CacheRegion.cache_on_arguments`.
1320 :param should_cache_fn: passed to :meth:`.CacheRegion.get_or_create`.
1322 .. versionadded:: 0.4.3
1324 :param to_str: callable, will be called on each function argument
1325 in order to convert to a string. Defaults to ``str()``. If the
1326 function accepts non-ascii unicode arguments on Python 2.x, the
1327 ``unicode()`` builtin can be substituted, but note this will
1328 produce unicode cache keys which may require key mangling before
1329 reaching the cache.
1331 .. versionadded:: 0.5.0
1333 :param function_key_generator: a function that will produce a
1334 "cache key". This function will supersede the one configured on the
1335 :class:`.CacheRegion` itself.
1337 .. versionadded:: 0.5.5
1339 .. seealso::
1341 :meth:`.CacheRegion.cache_multi_on_arguments`
1343 :meth:`.CacheRegion.get_or_create`
1345 """
1346 expiration_time_is_callable = compat.callable(expiration_time)
1348 if function_key_generator is None:
1349 function_key_generator = self.function_key_generator
1351 def get_or_create_for_user_func(key_generator, user_func, *arg, **kw):
1352 key = key_generator(*arg, **kw)
1354 timeout = (
1355 expiration_time()
1356 if expiration_time_is_callable
1357 else expiration_time
1358 )
1359 return self.get_or_create(
1360 key, user_func, timeout, should_cache_fn, (arg, kw)
1361 )
1363 def cache_decorator(user_func):
1364 if to_str is compat.string_type:
1365 # backwards compatible
1366 key_generator = function_key_generator(namespace, user_func)
1367 else:
1368 key_generator = function_key_generator(
1369 namespace, user_func, to_str=to_str
1370 )
1372 def refresh(*arg, **kw):
1373 """
1374 Like invalidate, but regenerates the value instead
1375 """
1376 key = key_generator(*arg, **kw)
1377 value = user_func(*arg, **kw)
1378 self.set(key, value)
1379 return value
1381 def invalidate(*arg, **kw):
1382 key = key_generator(*arg, **kw)
1383 self.delete(key)
1385 def set_(value, *arg, **kw):
1386 key = key_generator(*arg, **kw)
1387 self.set(key, value)
1389 def get(*arg, **kw):
1390 key = key_generator(*arg, **kw)
1391 return self.get(key)
1393 user_func.set = set_
1394 user_func.invalidate = invalidate
1395 user_func.get = get
1396 user_func.refresh = refresh
1397 user_func.original = user_func
1399 # Use `decorate` to preserve the signature of :param:`user_func`.
1401 return decorate(
1402 user_func, partial(get_or_create_for_user_func, key_generator)
1403 )
1405 return cache_decorator
1407 def cache_multi_on_arguments(
1408 self,
1409 namespace=None,
1410 expiration_time=None,
1411 should_cache_fn=None,
1412 asdict=False,
1413 to_str=compat.string_type,
1414 function_multi_key_generator=None,
1415 ):
1416 """A function decorator that will cache multiple return
1417 values from the function using a sequence of keys derived from the
1418 function itself and the arguments passed to it.
1420 This method is the "multiple key" analogue to the
1421 :meth:`.CacheRegion.cache_on_arguments` method.
1423 Example::
1425 @someregion.cache_multi_on_arguments()
1426 def generate_something(*keys):
1427 return [
1428 somedatabase.query(key)
1429 for key in keys
1430 ]
1432 The decorated function can be called normally. The decorator
1433 will produce a list of cache keys using a mechanism similar to
1434 that of :meth:`.CacheRegion.cache_on_arguments`, combining the
1435 name of the function with the optional namespace and with the
1436 string form of each key. It will then consult the cache using
1437 the same mechanism as that of :meth:`.CacheRegion.get_multi`
1438 to retrieve all current values; the originally passed keys
1439 corresponding to those values which aren't generated or need
1440 regeneration will be assembled into a new argument list, and
1441 the decorated function is then called with that subset of
1442 arguments.
1444 The returned result is a list::
1446 result = generate_something("key1", "key2", "key3")
1448 The decorator internally makes use of the
1449 :meth:`.CacheRegion.get_or_create_multi` method to access the
1450 cache and conditionally call the function. See that
1451 method for additional behavioral details.
1453 Unlike the :meth:`.CacheRegion.cache_on_arguments` method,
1454 :meth:`.CacheRegion.cache_multi_on_arguments` works only with
1455 a single function signature, one which takes a simple list of
1456 keys as arguments.
1458 Like :meth:`.CacheRegion.cache_on_arguments`, the decorated function
1459 is also provided with a ``set()`` method, which here accepts a
1460 mapping of keys and values to set in the cache::
1462 generate_something.set({"k1": "value1",
1463 "k2": "value2", "k3": "value3"})
1465 ...an ``invalidate()`` method, which has the effect of deleting
1466 the given sequence of keys using the same mechanism as that of
1467 :meth:`.CacheRegion.delete_multi`::
1469 generate_something.invalidate("k1", "k2", "k3")
1471 ...a ``refresh()`` method, which will call the creation
1472 function, cache the new values, and return them::
1474 values = generate_something.refresh("k1", "k2", "k3")
1476 ...and a ``get()`` method, which will return values
1477 based on the given arguments::
1479 values = generate_something.get("k1", "k2", "k3")
1481 .. versionadded:: 0.5.3 Added ``get()`` method to decorated
1482 function.
1484 Parameters passed to :meth:`.CacheRegion.cache_multi_on_arguments`
1485 have the same meaning as those passed to
1486 :meth:`.CacheRegion.cache_on_arguments`.
1488 :param namespace: optional string argument which will be
1489 established as part of each cache key.
1491 :param expiration_time: if not None, will override the normal
1492 expiration time. May be passed as an integer or a
1493 callable.
1495 :param should_cache_fn: passed to
1496 :meth:`.CacheRegion.get_or_create_multi`. This function is given a
1497 value as returned by the creator, and only if it returns True will
1498 that value be placed in the cache.
1500 :param asdict: if ``True``, the decorated function should return
1501 its result as a dictionary of keys->values, and the final result
1502 of calling the decorated function will also be a dictionary.
1503 If left at its default value of ``False``, the decorated function
1504 should return its result as a list of values, and the final
1505 result of calling the decorated function will also be a list.
1507 When ``asdict==True`` if the dictionary returned by the decorated
1508 function is missing keys, those keys will not be cached.
1510 :param to_str: callable, will be called on each function argument
1511 in order to convert to a string. Defaults to ``str()``. If the
1512 function accepts non-ascii unicode arguments on Python 2.x, the
1513 ``unicode()`` builtin can be substituted, but note this will
1514 produce unicode cache keys which may require key mangling before
1515 reaching the cache.
1517 .. versionadded:: 0.5.0
1519 :param function_multi_key_generator: a function that will produce a
1520 list of keys. This function will supersede the one configured on the
1521 :class:`.CacheRegion` itself.
1523 .. versionadded:: 0.5.5
1525 .. seealso::
1527 :meth:`.CacheRegion.cache_on_arguments`
1529 :meth:`.CacheRegion.get_or_create_multi`
1531 """
1532 expiration_time_is_callable = compat.callable(expiration_time)
1534 if function_multi_key_generator is None:
1535 function_multi_key_generator = self.function_multi_key_generator
1537 def get_or_create_for_user_func(key_generator, user_func, *arg, **kw):
1538 cache_keys = arg
1539 keys = key_generator(*arg, **kw)
1540 key_lookup = dict(zip(keys, cache_keys))
1542 @wraps(user_func)
1543 def creator(*keys_to_create):
1544 return user_func(*[key_lookup[k] for k in keys_to_create])
1546 timeout = (
1547 expiration_time()
1548 if expiration_time_is_callable
1549 else expiration_time
1550 )
1552 if asdict:
1554 def dict_create(*keys):
1555 d_values = creator(*keys)
1556 return [
1557 d_values.get(key_lookup[k], NO_VALUE) for k in keys
1558 ]
1560 def wrap_cache_fn(value):
1561 if value is NO_VALUE:
1562 return False
1563 elif not should_cache_fn:
1564 return True
1565 else:
1566 return should_cache_fn(value)
1568 result = self.get_or_create_multi(
1569 keys, dict_create, timeout, wrap_cache_fn
1570 )
1571 result = dict(
1572 (k, v)
1573 for k, v in zip(cache_keys, result)
1574 if v is not NO_VALUE
1575 )
1576 else:
1577 result = self.get_or_create_multi(
1578 keys, creator, timeout, should_cache_fn
1579 )
1581 return result
1583 def cache_decorator(user_func):
1584 key_generator = function_multi_key_generator(
1585 namespace, user_func, to_str=to_str
1586 )
1588 def invalidate(*arg):
1589 keys = key_generator(*arg)
1590 self.delete_multi(keys)
1592 def set_(mapping):
1593 keys = list(mapping)
1594 gen_keys = key_generator(*keys)
1595 self.set_multi(
1596 dict(
1597 (gen_key, mapping[key])
1598 for gen_key, key in zip(gen_keys, keys)
1599 )
1600 )
1602 def get(*arg):
1603 keys = key_generator(*arg)
1604 return self.get_multi(keys)
1606 def refresh(*arg):
1607 keys = key_generator(*arg)
1608 values = user_func(*arg)
1609 if asdict:
1610 self.set_multi(dict(zip(keys, [values[a] for a in arg])))
1611 return values
1612 else:
1613 self.set_multi(dict(zip(keys, values)))
1614 return values
1616 user_func.set = set_
1617 user_func.invalidate = invalidate
1618 user_func.refresh = refresh
1619 user_func.get = get
1621 # Use `decorate` to preserve the signature of :param:`user_func`.
1623 return decorate(
1624 user_func, partial(get_or_create_for_user_func, key_generator)
1625 )
1627 return cache_decorator
1630def make_region(*arg, **kw):
1631 """Instantiate a new :class:`.CacheRegion`.
1633 Currently, :func:`.make_region` is a passthrough
1634 to :class:`.CacheRegion`. See that class for
1635 constructor arguments.
1637 """
1638 return CacheRegion(*arg, **kw)