1from __future__ import with_statement 2 3import contextlib 4import datetime 5from functools import partial 6from functools import wraps 7import logging 8from numbers import Number 9import threading 10import time 11 12from decorator import decorate 13 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 30 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. 35 36""" 37 38log = logging.getLogger(__name__) 39 40 41class RegionInvalidationStrategy(object): 42 """Region invalidation strategy interface 43 44 Implement this interface and pass implementation instance 45 to :meth:`.CacheRegion.configure` to override default region invalidation. 46 47 Example:: 48 49 class CustomInvalidationStrategy(RegionInvalidationStrategy): 50 51 def __init__(self): 52 self._soft_invalidated = None 53 self._hard_invalidated = None 54 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 62 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)) 68 69 def was_hard_invalidated(self): 70 return bool(self._hard_invalidated) 71 72 def is_hard_invalidated(self, timestamp): 73 return (self._hard_invalidated and 74 timestamp < self._hard_invalidated) 75 76 def was_soft_invalidated(self): 77 return bool(self._soft_invalidated) 78 79 def is_soft_invalidated(self, timestamp): 80 return (self._soft_invalidated and 81 timestamp < self._soft_invalidated) 82 83 The custom implementation is injected into a :class:`.CacheRegion` 84 at configure time using the 85 :paramref:`.CacheRegion.configure.region_invalidator` parameter:: 86 87 region = CacheRegion() 88 89 region = region.configure(region_invalidator=CustomInvalidationStrategy()) # noqa 90 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:: 94 95 class MyInvalidator(RegionInvalidationStrategy): 96 def __init__(self, region): 97 self.region = region 98 # ... 99 100 # ... 101 102 region = CacheRegion() 103 region = region.configure(region_invalidator=MyInvalidator(region)) 104 105 .. versionadded:: 0.6.2 106 107 .. seealso:: 108 109 :paramref:`.CacheRegion.configure.region_invalidator` 110 111 """ 112 113 def invalidate(self, hard=True): 114 """Region invalidation. 115 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. 120 121 """ 122 123 raise NotImplementedError() 124 125 def is_hard_invalidated(self, timestamp): 126 """Check timestamp to determine if it was hard invalidated. 127 128 :return: Boolean. True if ``timestamp`` is older than 129 the last region invalidation time and region is invalidated 130 in hard mode. 131 132 """ 133 134 raise NotImplementedError() 135 136 def is_soft_invalidated(self, timestamp): 137 """Check timestamp to determine if it was soft invalidated. 138 139 :return: Boolean. True if ``timestamp`` is older than 140 the last region invalidation time and region is invalidated 141 in soft mode. 142 143 """ 144 145 raise NotImplementedError() 146 147 def is_invalidated(self, timestamp): 148 """Check timestamp to determine if it was invalidated. 149 150 :return: Boolean. True if ``timestamp`` is older than 151 the last region invalidation time. 152 153 """ 154 155 raise NotImplementedError() 156 157 def was_soft_invalidated(self): 158 """Indicate the region was invalidated in soft mode. 159 160 :return: Boolean. True if region was invalidated in soft mode. 161 162 """ 163 164 raise NotImplementedError() 165 166 def was_hard_invalidated(self): 167 """Indicate the region was invalidated in hard mode. 168 169 :return: Boolean. True if region was invalidated in hard mode. 170 171 """ 172 173 raise NotImplementedError() 174 175 176class DefaultInvalidationStrategy(RegionInvalidationStrategy): 177 def __init__(self): 178 self._is_hard_invalidated = None 179 self._invalidated = None 180 181 def invalidate(self, hard=True): 182 self._is_hard_invalidated = bool(hard) 183 self._invalidated = time.time() 184 185 def is_invalidated(self, timestamp): 186 return self._invalidated is not None and timestamp < self._invalidated 187 188 def was_hard_invalidated(self): 189 return self._is_hard_invalidated is True 190 191 def is_hard_invalidated(self, timestamp): 192 return self.was_hard_invalidated() and self.is_invalidated(timestamp) 193 194 def was_soft_invalidated(self): 195 return self._is_hard_invalidated is False 196 197 def is_soft_invalidated(self, timestamp): 198 return self.was_soft_invalidated() and self.is_invalidated(timestamp) 199 200 201class CacheRegion(object): 202 r"""A front end to a particular cache backend. 203 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:: 216 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 222 223 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 ) 233 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:: 239 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 247 248 249 Where the decorator might be used as:: 250 251 @my_region.cache_on_arguments(namespace=('x', 'y')) 252 def my_function(a, b, **kw): 253 return my_data() 254 255 .. seealso:: 256 257 :func:`.function_key_generator` - default key generator 258 259 :func:`.kwarg_function_key_generator` - optional gen that also 260 uses keyword arguments 261 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:: 266 267 def my_multi_key_generator(namespace, fn, **kw): 268 namespace = fn.__name__ + (namespace or '') 269 270 def generate_keys(*args): 271 return [namespace + ':' + str(a) for a in args] 272 273 return generate_keys 274 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. 295 296 For a specific example using async_creation_runner, new values can 297 be created in a background thread like so:: 298 299 import threading 300 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() 309 310 thread = threading.Thread(target=runner) 311 thread.start() 312 313 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 ) 324 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. 331 332 By default the async_creation_runner is disabled and is set 333 to ``None``. 334 335 .. versionadded:: 0.4.2 added the async_creation_runner 336 feature. 337 338 """ 339 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() 355 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`. 368 369 The :class:`.CacheRegion` itself 370 is returned. 371 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. 375 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. 379 380 .. versionadded 0.5.0 381 ``expiration_time`` may be optionally passed as a 382 ``datetime.timedelta`` value. 383 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. 389 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. 393 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. 398 399 .. versionadded:: 0.5.0 400 401 .. seealso:: 402 403 :ref:`changing_backend_behavior` 404 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. 408 409 .. versionadded:: 0.5.7 410 411 :param region_invalidator: Optional. Override default invalidation 412 strategy with custom implementation of 413 :class:`.RegionInvalidationStrategy`. 414 415 .. versionadded:: 0.6.2 416 417 """ 418 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 ) 426 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 ) 433 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 {}) 440 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 ) 451 452 if not self._user_defined_key_mangler: 453 self.key_mangler = self.backend.key_mangler 454 455 self._lock_registry = NameRegistry(self._create_mutex) 456 457 if getattr(wrap, "__iter__", False): 458 for wrapper in reversed(wrap): 459 self.wrap(wrapper) 460 461 if region_invalidator: 462 self.region_invalidator = region_invalidator 463 464 return self 465 466 def wrap(self, proxy): 467 """ Takes a ProxyBackend instance or class and wraps the 468 attached backend. """ 469 470 # if we were passed a type rather than an instance then 471 # initialize it. 472 if type(proxy) == type: 473 proxy = proxy() 474 475 if not issubclass(type(proxy), ProxyBackend): 476 raise TypeError( 477 "Type %s is not a valid ProxyBackend" % type(proxy) 478 ) 479 480 self.backend = proxy.wrap(self.backend) 481 482 def _mutex(self, key): 483 return self._lock_registry.get(key) 484 485 class _LockWrapper(object): 486 """weakref-capable wrapper for threading.Lock""" 487 488 def __init__(self): 489 self.lock = threading.Lock() 490 491 def acquire(self, wait=True): 492 return self.lock.acquire(wait) 493 494 def release(self): 495 self.lock.release() 496 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() 503 504 # cached value 505 _actual_backend = None 506 507 @property 508 def actual_backend(self): 509 """Return the ultimate backend underneath any proxies. 510 511 The backend might be the result of one or more ``proxy.wrap`` 512 applications. If so, derive the actual underlying backend. 513 514 .. versionadded:: 0.6.6 515 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 523 524 def invalidate(self, hard=True): 525 """Invalidate this :class:`.CacheRegion`. 526 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`.** 535 536 .. warning:: 537 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`. 546 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. 551 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. 558 559 Usage of "soft" invalidation requires that the region or the method 560 is given a non-None expiration time. 561 562 .. versionadded:: 0.3.0 563 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. 568 569 .. versionadded:: 0.5.1 570 571 """ 572 self.region_invalidator.invalidate(hard) 573 574 def configure_from_config(self, config_dict, prefix): 575 """Configure from a configuration dictionary 576 and a prefix. 577 578 Example:: 579 580 local_region = make_region() 581 memcached_region = make_region() 582 583 # regions are ready to use for function 584 # decorators, but not yet for actual caching 585 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.") 596 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 ) 611 612 @memoized_property 613 def backend(self): 614 raise exception.RegionNotConfigured( 615 "No backend is configured on this region." 616 ) 617 618 @property 619 def is_configured(self): 620 """Return True if the backend has been configured via the 621 :meth:`.CacheRegion.configure` method already. 622 623 .. versionadded:: 0.5.1 624 625 """ 626 return "backend" in self.__dict__ 627 628 def get(self, key, expiration_time=None, ignore_expiration=False): 629 """Return a value from the cache, based on the given key. 630 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``. 634 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. 643 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. 648 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. 656 657 .. versionadded:: 0.3.0 658 Support for the :meth:`.CacheRegion.invalidate` 659 method. 660 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. 666 667 :param expiration_time: Optional expiration time value 668 which will supersede that configured on the :class:`.CacheRegion` 669 itself. 670 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. 677 678 .. versionadded:: 0.3.0 679 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. 684 685 .. versionadded:: 0.3.0 686 687 .. seealso:: 688 689 :meth:`.CacheRegion.get_multi` 690 691 :meth:`.CacheRegion.get_or_create` 692 693 :meth:`.CacheRegion.set` 694 695 :meth:`.CacheRegion.delete` 696 697 698 """ 699 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 ) 706 707 return value.payload 708 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 715 716 current_time = time.time() 717 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 732 733 return value_fn 734 735 def get_multi(self, keys, expiration_time=None, ignore_expiration=False): 736 """Return multiple values from the cache, based on the given keys. 737 738 Returns values as a list matching the keys given. 739 740 E.g.:: 741 742 values = region.get_multi(["one", "two", "three"]) 743 744 To convert values to a dictionary, use ``zip()``:: 745 746 keys = ["one", "two", "three"] 747 values = region.get_multi(keys) 748 dictionary = dict(zip(keys, values)) 749 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``. 754 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. 763 764 .. versionadded:: 0.5.0 765 766 """ 767 if not keys: 768 return [] 769 770 if self.key_mangler: 771 keys = list(map(lambda key: self.key_mangler(key), keys)) 772 773 backend_values = self.backend.get_multi(keys) 774 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 ] 784 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 ) 795 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 805 806 return True 807 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. 817 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. 822 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. 831 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. 838 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`. 842 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. 848 849 :param creator: function which creates a new value. 850 851 :param creator_args: optional tuple of (args, kwargs) that will be 852 passed to the creator function if present. 853 854 .. versionadded:: 0.7.0 855 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. 859 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. 866 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.:: 872 873 def dont_cache_none(value): 874 return value is not None 875 876 value = region.get_or_create("some key", 877 create_value, 878 should_cache_fn=dont_cache_none) 879 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. 883 884 .. versionadded:: 0.4.3 885 886 .. seealso:: 887 888 :meth:`.CacheRegion.get` 889 890 :meth:`.CacheRegion.cache_on_arguments` - applies 891 :meth:`.get_or_create` to any function using a decorator. 892 893 :meth:`.CacheRegion.get_or_create_multi` - multiple key/value 894 version 895 896 """ 897 orig_key = key 898 if self.key_mangler: 899 key = self.key_mangler(key) 900 901 def get_value(): 902 value = self.backend.get(key) 903 if self._is_cache_miss(value, orig_key): 904 raise NeedRegenerationException() 905 906 ct = value.metadata["ct"] 907 if self.region_invalidator.is_soft_invalidated(ct): 908 ct = time.time() - expiration_time - 0.0001 909 910 return value.payload, ct 911 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) 921 922 if not should_cache_fn or should_cache_fn(created_value): 923 self.backend.set(key, value) 924 925 return value.payload, value.metadata["ct"] 926 927 if expiration_time is None: 928 expiration_time = self.expiration_time 929 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 ) 937 938 if expiration_time == -1: 939 expiration_time = None 940 941 if self.async_creation_runner: 942 943 def async_creator(mutex): 944 if creator_args: 945 946 @wraps(creator) 947 def go(): 948 return creator(*creator_args[0], **creator_args[1]) 949 950 else: 951 go = creator 952 return self.async_creation_runner(self, orig_key, go, mutex) 953 954 else: 955 async_creator = None 956 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 965 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. 970 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. 977 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. 981 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. 988 989 :param keys: Sequence of keys to be retrieved. 990 991 :param creator: function which accepts a sequence of keys and 992 returns a sequence of new values. 993 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. 997 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. 1002 1003 .. versionadded:: 0.5.0 1004 1005 .. seealso:: 1006 1007 1008 :meth:`.CacheRegion.cache_multi_on_arguments` 1009 1010 :meth:`.CacheRegion.get_or_create` 1011 1012 """ 1013 1014 def get_value(key): 1015 value = values.get(key, NO_VALUE) 1016 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 1026 1027 return value.payload, ct 1028 1029 def gen_value(): 1030 raise NotImplementedError() 1031 1032 def async_creator(key, mutex): 1033 mutexes[key] = mutex 1034 1035 if expiration_time is None: 1036 expiration_time = self.expiration_time 1037 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 ) 1045 1046 if expiration_time == -1: 1047 expiration_time = None 1048 1049 mutexes = {} 1050 1051 sorted_unique_keys = sorted(set(keys)) 1052 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 1057 1058 orig_to_mangled = dict(zip(sorted_unique_keys, mangled_keys)) 1059 1060 values = dict(zip(mangled_keys, self.backend.get_multi(mangled_keys))) 1061 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) 1076 1077 with self._log_time(keys_to_get): 1078 new_values = creator(*keys_to_get) 1079 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 ) 1084 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 ) 1093 1094 if values_to_cache: 1095 self.backend.set_multi(values_to_cache) 1096 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() 1102 1103 def _value(self, value): 1104 """Return a :class:`.CachedValue` given a value.""" 1105 return CachedValue(value, {"ct": time.time(), "v": value_version}) 1106 1107 def set(self, key, value): 1108 """Place a new value in the cache under the given key.""" 1109 1110 if self.key_mangler: 1111 key = self.key_mangler(key) 1112 self.backend.set(key, self._value(value)) 1113 1114 def set_multi(self, mapping): 1115 """Place new values in the cache under the given keys. 1116 1117 .. versionadded:: 0.5.0 1118 1119 """ 1120 if not mapping: 1121 return 1122 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) 1131 1132 def delete(self, key): 1133 """Remove a value from the cache. 1134 1135 This operation is idempotent (can be called multiple times, or on a 1136 non-existent key, safely) 1137 """ 1138 1139 if self.key_mangler: 1140 key = self.key_mangler(key) 1141 1142 self.backend.delete(key) 1143 1144 def delete_multi(self, keys): 1145 """Remove multiple values from the cache. 1146 1147 This operation is idempotent (can be called multiple times, or on a 1148 non-existent key, safely) 1149 1150 .. versionadded:: 0.5.0 1151 1152 """ 1153 1154 if self.key_mangler: 1155 keys = list(map(lambda key: self.key_mangler(key), keys)) 1156 1157 self.backend.delete_multi(keys) 1158 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. 1170 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. 1175 1176 E.g.:: 1177 1178 @someregion.cache_on_arguments() 1179 def generate_something(x, y): 1180 return somedatabase.query(x, y) 1181 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:: 1185 1186 result = generate_something(5, 6) 1187 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:: 1192 1193 generate_something.invalidate(5, 6) 1194 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:: 1202 1203 generate_something.set(3, 5, 6) 1204 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. 1208 1209 .. versionadded:: 0.4.1 Added ``set()`` method to decorated function. 1210 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:: 1214 1215 newvalue = generate_something.refresh(5, 6) 1216 1217 .. versionadded:: 0.5.0 Added ``refresh()`` method to decorated 1218 function. 1219 1220 ``original()`` on other hand will invoke the decorated function 1221 without any caching:: 1222 1223 newvalue = generate_something.original(5, 6) 1224 1225 .. versionadded:: 0.6.0 Added ``original()`` method to decorated 1226 function. 1227 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:: 1231 1232 value = generate_something.get(5, 6) 1233 1234 .. versionadded:: 0.5.3 Added ``get()`` method to decorated 1235 function. 1236 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. 1241 1242 Given a function ``one`` inside the module 1243 ``myapp.tools``:: 1244 1245 @region.cache_on_arguments(namespace="foo") 1246 def one(a, b): 1247 return a + b 1248 1249 Above, calling ``one(3, 4)`` will produce a 1250 cache key as follows:: 1251 1252 myapp.tools:one|foo|3 4 1253 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:: 1258 1259 class MyClass(object): 1260 @region.cache_on_arguments(namespace="foo") 1261 def one(self, a, b): 1262 return a + b 1263 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. 1267 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:: 1272 1273 class MyClass(object): 1274 @region.cache_on_arguments(namespace='MC') 1275 def somemethod(self, x, y): 1276 "" 1277 1278 class MyOtherClass(object): 1279 @region.cache_on_arguments(namespace='MOC') 1280 def somemethod(self, x, y): 1281 "" 1282 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. 1289 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`. 1295 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. 1303 1304 :param expiration_time: if not None, will override the normal 1305 expiration time. 1306 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". 1315 1316 .. versionchanged:: 0.5.0 1317 ``expiration_time`` may be passed as a callable to 1318 :meth:`.CacheRegion.cache_on_arguments`. 1319 1320 :param should_cache_fn: passed to :meth:`.CacheRegion.get_or_create`. 1321 1322 .. versionadded:: 0.4.3 1323 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. 1330 1331 .. versionadded:: 0.5.0 1332 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. 1336 1337 .. versionadded:: 0.5.5 1338 1339 .. seealso:: 1340 1341 :meth:`.CacheRegion.cache_multi_on_arguments` 1342 1343 :meth:`.CacheRegion.get_or_create` 1344 1345 """ 1346 expiration_time_is_callable = compat.callable(expiration_time) 1347 1348 if function_key_generator is None: 1349 function_key_generator = self.function_key_generator 1350 1351 def get_or_create_for_user_func(key_generator, user_func, *arg, **kw): 1352 key = key_generator(*arg, **kw) 1353 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 ) 1362 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 ) 1371 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 1380 1381 def invalidate(*arg, **kw): 1382 key = key_generator(*arg, **kw) 1383 self.delete(key) 1384 1385 def set_(value, *arg, **kw): 1386 key = key_generator(*arg, **kw) 1387 self.set(key, value) 1388 1389 def get(*arg, **kw): 1390 key = key_generator(*arg, **kw) 1391 return self.get(key) 1392 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 1398 1399 # Use `decorate` to preserve the signature of :param:`user_func`. 1400 1401 return decorate( 1402 user_func, partial(get_or_create_for_user_func, key_generator) 1403 ) 1404 1405 return cache_decorator 1406 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. 1419 1420 This method is the "multiple key" analogue to the 1421 :meth:`.CacheRegion.cache_on_arguments` method. 1422 1423 Example:: 1424 1425 @someregion.cache_multi_on_arguments() 1426 def generate_something(*keys): 1427 return [ 1428 somedatabase.query(key) 1429 for key in keys 1430 ] 1431 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. 1443 1444 The returned result is a list:: 1445 1446 result = generate_something("key1", "key2", "key3") 1447 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. 1452 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. 1457 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:: 1461 1462 generate_something.set({"k1": "value1", 1463 "k2": "value2", "k3": "value3"}) 1464 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`:: 1468 1469 generate_something.invalidate("k1", "k2", "k3") 1470 1471 ...a ``refresh()`` method, which will call the creation 1472 function, cache the new values, and return them:: 1473 1474 values = generate_something.refresh("k1", "k2", "k3") 1475 1476 ...and a ``get()`` method, which will return values 1477 based on the given arguments:: 1478 1479 values = generate_something.get("k1", "k2", "k3") 1480 1481 .. versionadded:: 0.5.3 Added ``get()`` method to decorated 1482 function. 1483 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`. 1487 1488 :param namespace: optional string argument which will be 1489 established as part of each cache key. 1490 1491 :param expiration_time: if not None, will override the normal 1492 expiration time. May be passed as an integer or a 1493 callable. 1494 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. 1499 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. 1506 1507 When ``asdict==True`` if the dictionary returned by the decorated 1508 function is missing keys, those keys will not be cached. 1509 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. 1516 1517 .. versionadded:: 0.5.0 1518 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. 1522 1523 .. versionadded:: 0.5.5 1524 1525 .. seealso:: 1526 1527 :meth:`.CacheRegion.cache_on_arguments` 1528 1529 :meth:`.CacheRegion.get_or_create_multi` 1530 1531 """ 1532 expiration_time_is_callable = compat.callable(expiration_time) 1533 1534 if function_multi_key_generator is None: 1535 function_multi_key_generator = self.function_multi_key_generator 1536 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)) 1541 1542 @wraps(user_func) 1543 def creator(*keys_to_create): 1544 return user_func(*[key_lookup[k] for k in keys_to_create]) 1545 1546 timeout = ( 1547 expiration_time() 1548 if expiration_time_is_callable 1549 else expiration_time 1550 ) 1551 1552 if asdict: 1553 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 ] 1559 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) 1567 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 ) 1580 1581 return result 1582 1583 def cache_decorator(user_func): 1584 key_generator = function_multi_key_generator( 1585 namespace, user_func, to_str=to_str 1586 ) 1587 1588 def invalidate(*arg): 1589 keys = key_generator(*arg) 1590 self.delete_multi(keys) 1591 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 ) 1601 1602 def get(*arg): 1603 keys = key_generator(*arg) 1604 return self.get_multi(keys) 1605 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 1615 1616 user_func.set = set_ 1617 user_func.invalidate = invalidate 1618 user_func.refresh = refresh 1619 user_func.get = get 1620 1621 # Use `decorate` to preserve the signature of :param:`user_func`. 1622 1623 return decorate( 1624 user_func, partial(get_or_create_for_user_func, key_generator) 1625 ) 1626 1627 return cache_decorator 1628 1629 1630def make_region(*arg, **kw): 1631 """Instantiate a new :class:`.CacheRegion`. 1632 1633 Currently, :func:`.make_region` is a passthrough 1634 to :class:`.CacheRegion`. See that class for 1635 constructor arguments. 1636 1637 """ 1638 return CacheRegion(*arg, **kw) 1639