Skip to content

Instantly share code, notes, and snippets.

@xkstein
Last active November 15, 2022 00:22
Show Gist options
  • Select an option

  • Save xkstein/868036348e3fff2a01c57bba448a0f2b to your computer and use it in GitHub Desktop.

Select an option

Save xkstein/868036348e3fff2a01c57bba448a0f2b to your computer and use it in GitHub Desktop.
Little redis caching util
'''Two simple examples of redis caching
Includes:
- Simple function-type decorator
- Class with method for time dependent caching
They both make entries in redis with key [function name] + [bound args]
'''
from time import sleep, time
from functools import wraps, partial
from inspect import signature
import pickle
import redis
class Cache:
'''Util for caching function outputs to redis
Decorater which caches function output to the passed redis instance.
Includes method for only caching output for a given duration of time.
Args:
redis_client: redis object to cache to
Example:
cache = Cache(r)
@cache
def results_cached():
...
@cache.for_time(duration=15)
def results_cached_for_duration():
...
'''
def __init__(self, redis_client):
self.redis_client = redis_client
def _store(self, key, val, duration=None):
if duration:
self.redis_client.setex(key, duration, val)
return
self.redis_client.set(key, val)
def __call__(self, func, duration=None):
@wraps(func)
def wrapper(*args, **kwargs):
bound_args = signature(func).bind(*args, **kwargs)
bound_args.apply_defaults()
key = func.__qualname__ + str(bound_args.arguments)
if not self.redis_client.exists(key):
val = func(*args, **kwargs)
self._store(key, pickle.dumps(val), duration=duration)
return val
# Sort of a nasty work around if redis is set to decode_response
try:
val = self.redis_client.get(key)
except UnicodeDecodeError as e:
val = e.object
return pickle.loads(val)
return wrapper
def for_time(self, duration: int):
'''Decorator that caches function output for duration'''
assert isinstance(duration, int) and duration > 0,\
'The indicated duration has to be a positive integer'
return partial(self, duration=duration)
def cache_simple(func):
'''Simple redis caching, without any time stuff'''
@wraps(func)
def wrapper(*args, **kwargs):
bound_args = signature(func).bind(*args, **kwargs)
bound_args.apply_defaults()
key = func.__qualname__ + str(bound_args.arguments)
if not r.exists(key):
val = func(*args, **kwargs)
r.set(key, pickle.dumps(val))
return val
return pickle.loads(r.get(key))
return wrapper
r = redis.Redis()
cache = Cache(r)
@cache.for_time(1)
def request_from_changing_database():
return time()
@cache_simple
def fib(n, a=None, b=None):
if n == 0:
print('Computed Fib')
return 0
return fib(n - 1, a=a, b=b) + n
if __name__ == '__main__':
r.flushall()
print('fib(50): ', end='')
fib(50)
print('fib(50, b=None, a=None): ')
fib(50, b=None, a=None)
print('fib(50, a=None, b=None): ')
fib(50, a=None, b=None)
print('\nfib(50, a=1, b=None): ', end='')
fib(50, a=1, b=None)
print(f'\nRequest -> {request_from_changing_database()}')
print('Sleep 0.1')
sleep(0.1)
print(f'Request -> {request_from_changing_database()}')
print('Sleep 1')
sleep(1)
print(f'Request -> {request_from_changing_database()}')
# Output:
# fib(50): Computed Fib
# fib(50, b=None, a=None):
# fib(50, a=None, b=None):
#
# fib(50, a=1, b=None): Computed Fib
#
# Request -> 1668468370.6167119
# Sleep 0.1
# Request -> 1668468370.6167119
# Sleep 1
# Request -> 1668468371.724545
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment