Skip to content

Commit 074cb77

Browse files
Grégoire Deveauxzzzeek
authored andcommitted
add method to get the CachedValue / cached time directly
Added new method :meth:`.CacheRegion.get_value_metadata` which can be used to get a value from the cache along with its metadata, including timestamp of when the value was cached. The :class:`.CachedValue` object is returned which features new accessors to retrieve cached time and current age. Pull request courtesy Grégoire Deveaux. Fixes: #37 Closes: #247 Pull-request: #247 Pull-request-sha: e21961d Change-Id: I192b6768f1c6c1b4c75c122eb372cf89121e78ba
1 parent 8a49c06 commit 074cb77

File tree

4 files changed

+119
-3
lines changed

4 files changed

+119
-3
lines changed

docs/build/unreleased/37.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.. change::
2+
:tags: feature, region
3+
:tickets: 37
4+
5+
Added new method :meth:`.CacheRegion.get_value_metadata` which can be used
6+
to get a value from the cache along with its metadata, including timestamp
7+
of when the value was cached. The :class:`.CachedValue` object is returned
8+
which features new accessors to retrieve cached time and current age. Pull
9+
request courtesy Grégoire Deveaux.
10+
11+

dogpile/cache/api.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import abc
44
import pickle
5+
import time
56
from typing import Any
67
from typing import Callable
78
from typing import cast
@@ -125,8 +126,38 @@ class CachedValue(NamedTuple):
125126
"""
126127

127128
payload: ValuePayload
129+
"""the actual cached value."""
128130

129131
metadata: MetaDataType
132+
"""Metadata dictionary for the cached value.
133+
134+
Prefer using accessors such as :attr:`.CachedValue.cached_time` rather
135+
than accessing this mapping directly.
136+
137+
"""
138+
139+
@property
140+
def cached_time(self) -> float:
141+
"""The epoch (floating point time value) stored when this payload was
142+
cached.
143+
144+
.. versionadded:: 1.3
145+
146+
"""
147+
return cast(float, self.metadata["ct"])
148+
149+
@property
150+
def age(self) -> float:
151+
"""Returns the elapsed time in seconds as a `float` since the insertion
152+
of the value in the cache.
153+
154+
This value is computed **dynamically** by subtracting the cached
155+
floating point epoch value from the value of ``time.time()``.
156+
157+
.. versionadded:: 1.3
158+
159+
"""
160+
return time.time() - self.cached_time
130161

131162

132163
CacheReturnType = Union[CachedValue, NoValue]

dogpile/cache/region.py

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from typing import Sequence
1818
from typing import Tuple
1919
from typing import Type
20+
from typing import TYPE_CHECKING
2021
from typing import Union
2122

2223
from decorator import decorate
@@ -700,7 +701,12 @@ def is_configured(self):
700701
"""
701702
return "backend" in self.__dict__
702703

703-
def get(self, key, expiration_time=None, ignore_expiration=False):
704+
def get(
705+
self,
706+
key: KeyType,
707+
expiration_time: Optional[float] = None,
708+
ignore_expiration: bool = False,
709+
) -> CacheReturnType:
704710
"""Return a value from the cache, based on the given key.
705711
706712
If the value is not present, the method returns the token
@@ -771,15 +777,49 @@ def get(self, key, expiration_time=None, ignore_expiration=False):
771777
772778
773779
"""
780+
value = self._get_cache_value(key, expiration_time, ignore_expiration)
781+
return value.payload
782+
783+
def get_value_metadata(
784+
self,
785+
key: KeyType,
786+
expiration_time: Optional[float] = None,
787+
ignore_expiration: bool = False,
788+
) -> Optional[CachedValue]:
789+
"""Return the :class:`.CachedValue` object directly from the cache.
790+
791+
This is the enclosing datastructure that includes the value as well as
792+
the metadata, including the timestamp when the value was cached.
793+
Convenience accessors on :class:`.CachedValue` also provide for common
794+
data such as :attr:`.CachedValue.cached_time` and
795+
:attr:`.CachedValue.age`.
796+
774797
798+
.. versionadded:: 1.3. Added :meth:`.CacheRegion.get_value_metadata`
799+
"""
800+
cache_value = self._get_cache_value(
801+
key, expiration_time, ignore_expiration
802+
)
803+
if cache_value is NO_VALUE:
804+
return None
805+
else:
806+
if TYPE_CHECKING:
807+
assert isinstance(cache_value, CachedValue)
808+
return cache_value
809+
810+
def _get_cache_value(
811+
self,
812+
key: KeyType,
813+
expiration_time: Optional[float] = None,
814+
ignore_expiration: bool = False,
815+
) -> CacheReturnType:
775816
if self.key_mangler:
776817
key = self.key_mangler(key)
777818
value = self._get_from_backend(key)
778819
value = self._unexpired_value_fn(expiration_time, ignore_expiration)(
779820
value
780821
)
781-
782-
return value.payload
822+
return value
783823

784824
def _unexpired_value_fn(self, expiration_time, ignore_expiration):
785825
if ignore_expiration:

tests/cache/test_region.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,40 @@ def test_should_delete_multiple_values(self):
533533
eq_(NO_VALUE, reg.get("key2"))
534534
eq_(values["key3"], reg.get("key3"))
535535

536+
def test_get_value_metadata(self):
537+
reg = self._region()
538+
with mock.patch("time.time", return_value=100):
539+
reg.set("some key", "some value")
540+
with mock.patch("time.time", return_value=105):
541+
value_metadata = reg.get_value_metadata("some key")
542+
eq_(value_metadata.payload, "some value")
543+
eq_(value_metadata.cached_time, 100)
544+
eq_(value_metadata.age, 5)
545+
546+
def test_get_value_metadata_no_value(self):
547+
reg = self._region()
548+
is_(reg.get_value_metadata("some key"), None)
549+
550+
def test_get_value_metadata_expired(self):
551+
reg = self._region()
552+
with mock.patch("time.time", return_value=100):
553+
reg.set("some key", "some value")
554+
with mock.patch("time.time", return_value=105):
555+
value_metadata = reg.get_value_metadata("some key", 4)
556+
is_(value_metadata, None)
557+
558+
def test_get_value_metadata_expiration_ignored(self):
559+
reg = self._region()
560+
with mock.patch("time.time", return_value=100):
561+
reg.set("some key", "some value")
562+
with mock.patch("time.time", return_value=105):
563+
value_metadata = reg.get_value_metadata(
564+
"some key", expiration_time=4, ignore_expiration=True
565+
)
566+
eq_(value_metadata.payload, "some value")
567+
eq_(value_metadata.cached_time, 100)
568+
eq_(value_metadata.age, 5)
569+
536570

537571
class ProxyRegionTest(RegionTest):
538572

0 commit comments

Comments
 (0)