TL;DR: Here’s a simple Python decorator I wrote which can cache function results with Redis.
Prerequisites
# python 3.11, tested on Mac OS X and Ubuntu Linux # installing redis-py pip install --user redis # running a redis container for local development # it's also ok to run redis directly docker run --rm -p 6379:6379 redis:7.0-alpine
The Python Decorator
What’s a decorator in Python? Here’s a very easy-to-understand explanation just for the question. Below is my code of the decorator with notes:
# redisHelper.py import redis import os # I always go for connection pooling whenever it's available redis_pool = redis.ConnectionPool(host=os.environ.get("REDIS_HOST", default='127.0.0.1'), port=6379, db=0) # I use the function name + all parameters to form a cache key # so whatever combination of values used to call a funtion will have its unique cache key # eg. cache key for myfunc('abc', 123) will be 'myfunc_abc_123' def args_to_key(*args, **kwargs): params = [arg.__name__ if callable(arg) else str(arg) for arg in args] + [str(kwarg) for kwarg in kwargs.values()] return "_".join(params) # the name of the decorator def redis_cached(func): def wrapper(*args, **kwargs): # reuse a connection from the pool r = redis.Redis(connection_pool=redis_pool) cache_key = args_to_key(func, *args, **kwargs) # test if a matching cache key exists cached = r.get(cache_key) if cached: # found in cache, return it # redis returns bytes, converted to string here return cached.decode('utf-8') # otherwise pass everything to the downstream function result = func(*args, **kwargs) # set cache time-to-live duration if there's a ttl parameter if 'ttl' in kwargs: ttl_seconds = kwargs['ttl'] # default ttl is 1 hour # after the ttl, the key will be removed from redis automatically else: ttl_seconds = 3600 # put the result from downstream function into cache, with a ttl # so next call with the same parameters will be handled by the cache r.setex(cache_key, ttl_seconds, result) # return the result transparently return result return wrapper # sample usage @redis_cached def test_redis(key1, key2): # the function body could be some database queries which worth caching return f"a string contains {key1} and {key2}"
Done 🙂