Skip to content

Commit

Permalink
Improve @redis_cache() (#56)
Browse files Browse the repository at this point in the history
* Set expiration on @redis_cache() hashes

Redis supports various volatile key eviction policies, which only evict
keys that have an expiration timeout set.  Let's hope that our users are
sane and use such a volatile key eviction policy; and set an expiration
on cache hashes, as these keys are presumably safer to evict than other
keys.

* Bump version number

* Preserve undecorated function as f.__wrapped__
  • Loading branch information
brainix authored Sep 13, 2017
1 parent 809e340 commit adb3e31
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 5 deletions.
2 changes: 1 addition & 1 deletion pottery/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@


__title__ = 'pottery'
__version__ = '0.44'
__version__ = '0.45'
__description__, __long_description__ = (
s.strip() for s in __doc__.split('\n\n', 1)
)
Expand Down
22 changes: 18 additions & 4 deletions pottery/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,36 @@



_DEFAULT_TIMEOUT = 365 * 24 * 60 * 60



def _arg_hash(*args, **kwargs):
return hash((args, frozenset(kwargs.items())))

def redis_cache(*, key, redis=None):


def redis_cache(*, key, redis=None, timeout=_DEFAULT_TIMEOUT):
'''Redis-backed caching decorator.
Arguments to the cached function must be hashable.
Access the underlying function with f.__wrapped__.
'''
redis = Redis() if redis is None else redis
cache = RedisDict(redis=redis, key=key)

def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
key = _arg_hash(*args, **kwargs)
hash_ = _arg_hash(*args, **kwargs)
try:
return_value = cache[key]
return_value = cache[hash_]
except KeyError:
return_value = func(*args, **kwargs)
cache[key] = return_value
cache[hash_] = return_value
redis.expire(key, timeout)
return return_value
wrapper.__wrapped__ = func
return wrapper
return decorator
20 changes: 20 additions & 0 deletions tests/test_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@


import random
import time

from pottery import RedisDict
from pottery import redis_cache
from pottery.base import _default_redis
from pottery.cache import _DEFAULT_TIMEOUT
from tests.base import TestCase


Expand Down Expand Up @@ -67,3 +69,21 @@ def test_cache(self):
assert value5 != value4
assert self.expensive_method('raj', last='shah') == value5
assert len(self.cache) == 4

def test_expiration(self):
self.expensive_method()
assert self.redis.ttl(self._KEY) == _DEFAULT_TIMEOUT
time.sleep(1)
assert self.redis.ttl(self._KEY) == _DEFAULT_TIMEOUT - 1

self.expensive_method()
assert self.redis.ttl(self._KEY) == _DEFAULT_TIMEOUT
time.sleep(1)
assert self.redis.ttl(self._KEY) == _DEFAULT_TIMEOUT - 1

self.expensive_method('raj')
assert self.redis.ttl(self._KEY) == _DEFAULT_TIMEOUT

def test_wrapped(self):
assert self.expensive_method() == self.expensive_method()
assert self.expensive_method() != self.expensive_method.__wrapped__()

0 comments on commit adb3e31

Please sign in to comment.