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