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

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

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

Powered by Google App Engine
This is Rietveld 408576698