A Python library that provides decorators for caching function results in Redis, supporting multiple serialization formats and caching strategies, as well as asynchronous operations.
redis_func_cache is a Python library that provides decorators for caching function results in Redis, similar to the caching functionality offered by the standard library. Like the functools module, it includes useful decorators such as lru_cache, which are valuable for implementing memoization.
Unlike in-process caches such as the standard library's functools.lru_cache — which are private to a single Python process — this library uses Redis as a distributed cache backend: a result computed once is shared by every process and machine behind your application, and survives restarts and deploys. Decorated functions keep their ordinary look and feel; the distributed part is handled by the library:
- Shared across processes and hosts — one cache for all workers, no per-process duplication.
- Works with any Redis deployment — standalone, Sentinel-managed replication, or Redis Cluster (dedicated policies pin each cache's keys to one hash slot). The library adds no infrastructure of its own.
- Atomic operations — every cache read/write is a single Lua script executed atomically by Redis, safe under high concurrency.
- Pluggable eviction policies — LRU, LFU, FIFO, GDSF, Hyperbolic, ...; each decorated function is an independent logical cache with its own algorithm and maxsize, chosen at decoration time.
Cache a function — async or sync — with one decorator:
from redis.asyncio import ConnectionPool, Redis
from redis_func_cache import RedisFuncCache as Cache
pool = ConnectionPool.from_url("redis://")
cache = Cache(__name__, factory=lambda: Redis.from_pool(pool))
@cache
async def get_data(): # your slow function — any function
await asyncio.sleep(10)
return "expensive result"
await get_data() # 10 s — computed and stored in Redis
await get_data() # 0.00x s — served from Redis, shared by every processEvery process and machine behind your application now shares this one cache. To run this yourself step by step — including starting a Redis server — see Quick Start.
- Built on redis-py, the official Python client for Redis — which is also the only runtime dependency (plus
typing-extensionson Python < 3.12); the optional serialization formats are extras, and each is used only if its package is installed and selected. - Simple decorator syntax supporting both
asyncand common functions, asynchronous and synchronous I/O. - Support Redis Cluster.
- Eviction policies: LRU, LFU, FIFO, MRU, RR, GDSF (cost-aware), Hyperbolic, LRU with random admission — pluggable, composable from keying / hasher / scripts components.
- Serialization formats: JSON, Pickle, Dill, MsgPack, YAML, BSON, CBOR, cloudpickle ...
- Optional handler extension around the four serialization boundaries — async-aware, settable per cache or per function, for patterns like offloading large values to object storage while Redis stores only a small reference.
- Per-item TTL (Redis ≥ 7.4).
- Maintenance operations:
vacuumto clean expired entries,purgeto drop all cache structures — both without blocking Redis.
The RedisFuncCache executes a decorated function with specified arguments and caches its result. Here's a breakdown of the steps:
- Initialize Scripts: Register the policy's two Lua scripts (cache hit and update) against the Redis client, cached per client.
- Locate the Call: Compute the call identity — key pair and hash value — once; the same identity is shared by the handler context and the get/put script invocation.
- Attempt Cache Retrieval: Attempt to retrieve a cached result. If a cache hit occurs, deserialize and return the cached result.
- Execute User Function: If no cache hit occurs, execute the decorated function with the provided arguments and keyword arguments.
- Store the Result: Serialize the result of the user function and store it in Redis.
- Return Result: Return the result of the decorated function.
Under the hood, the library combines a pair of Redis data structures to manage cache data:
-
The first is a sorted set, which stores the hash values of the decorated function calls along with a score for each item.
When the cache reaches its maximum size, the score is used to determine which item to evict.
-
The second is a hash map, which stores the hash values of the function calls and their corresponding return values.
This can be visualized as follows:
The main idea of the eviction policy is that the cache keys are stored in a set, and the cache values are stored in a hash map. Eviction is performed by removing the lowest-scoring item from the set, and then deleting the corresponding field and value from the hash map.
Here is an example showing how the LRU cache's eviction policy works (maximum size is 3):
pip install redis_func_cache[hiredis]hiredis is a strongly recommended optional extra that can significantly improve performance; Pygments is another — if installed, it shrinks the Lua scripts sent to the Redis server. For other installation methods (from source, from GitHub) see CONTRIBUTING.md.
Every cache read/write is a single Lua script executed atomically by the Redis server — safe under high concurrency with no extra locking. On Redis Cluster, atomicity holds as well: the cluster policies pin each cache's two keys to one hash slot on the same node.
The cache issues these scripts through the redis-py client you supply (directly or via a factory); it does not manage connections itself. Client thread safety, event-loop affinity and fork behavior are defined by redis-py — prefer the factory and pool pattern (lightweight clients sharing one pre-configured pool):
redis_pool = redis.ConnectionPool(...) # Use a pool, not a single client
factory = lambda: redis.from_pool(redis_pool) # Use a factory, not a static client
cache = RedisFuncCache(__name__, lru_policy, factory=factory)See Concurrency and Atomicity in the documentation for the full guidance (event loops, fork safety, function-level concurrency, contextual state isolation).
More documentation is available in the docs directory (and rendered at Read the Docs):
- Quick Start — prerequisites, a first cached function, choosing an eviction policy
- Eviction Policies — the built-in policies and presets, choosing and composing your own
- Advanced Usage — custom serializers, custom key formats and hash algorithms
- Handlers — the handler extension around the serialization boundaries (e.g. offloading large values to object storage)
- Configuration — cache size & TTL, per-item TTL, serialization,
excludes, multiple key pairs, cluster policies, cache mode control - Important Considerations — concurrency & atomicity, cache stampede risk, known issues and limitations
- Migration Guide — 0.x → 1.0 breaking changes and migration
Two explicit operations, both non-blocking for Redis:
vacuum— with a per-itemttl(Redis ≥ 7.4), expired results leave "ghost" entries in the sorted-set index;vacuumscans it in batches and removes them (avacuum()is the async mirror).purge— deletes every Redis key the cache owns, enumerating per-function key pairs withSCANand deleting in batches withUNLINK(apurge()is the async mirror).
Details and examples: Cache Maintenance.
See docs/considerations.md for the full list of known issues and limitations.
Start a Redis server, then run the test suite (a Docker Compose file in the docker directory can start Redis and run the whole suite for you). Detailed instructions — environment variables, the cluster test groups, and the Docker-based runner — are in CONTRIBUTING.md.
To set up a development environment, clone the repository and see CONTRIBUTING.md for the full setup (virtual environment, dependencies, pre-commit hooks — some of which invoke host tools such as uv, Node.js and lua-language-server — plus coding conventions and the architecture overview).
The library composes three orthogonal components into an eviction policy: Keying (key naming, with cluster hash-tag variants), Hasher (per-call sub-key computation) and Scripts (the Lua script declarations). Full module structure and class diagrams are documented in CONTRIBUTING.md.