How to Implement a Redis-Like Cache Dictionary in Python
Build a RedisMockDict class that mimics basic Redis key-value operations with TTL support, expiry cleanup, and standard dict-like methods.
Python code
80 linesfrom collections import OrderedDict
import time
class RedisMockDict:
def __init__(self, ttl=None):
self._data = OrderedDict()
self._ttl = ttl # default TTL in seconds, None = no expiry
self._expiry = {}
def set(self, key, value, ttl=None):
"""Set a key-value pair with optional TTL."""
self._data[key] = value
self._expiry[key] = time.time() + (ttl if ttl is not None else self._ttl or 0)
def get(self, key):
"""Get value; returns None if key missing or expired."""
if key not in self._data:
return None
expiry = self._expiry[key]
if expiry and time.time() > expiry:
self.delete(key)
return None
return self._data[key]
def delete(self, key):
"""Delete a key."""
if key in self._data:
del self._data[key]
del self._expiry[key]
def exists(self, key):
"""Check if key exists and is not expired."""
return self.get(key) is not None
def expire(self, key, seconds):
"""Set/update TTL for an existing key."""
if key in self._data:
self._expiry[key] = time.time() + seconds
def ttl(self, key):
"""Remaining TTL in seconds; -1 if no expiry, -2 if key missing."""
if key not in self._data:
return -2
expiry = self._expiry[key]
if not expiry:
return -1
remaining = expiry - time.time()
return max(int(remaining), 0)
def __len__(self):
"""Number of live keys."""
self._cleanup()
return len(self._data)
def _cleanup(self):
"""Remove expired keys."""
now = time.time()
expired = [k for k, e in self._expiry.items() if e and e <= now]
for k in expired:
self.delete(k)
def __repr__(self):
self._cleanup()
return f"RedisMockDict({dict(self._data)})"
if __name__ == "__main__":
cache = RedisMockDict()
cache.set("user:1", {"name": "Alice"}, ttl=2)
cache.set("user:2", "Bob")
print("Get user:1:", cache.get("user:1"))
print("TLL user:1:", cache.ttl("user:1"))
time.sleep(3)
print("After expiry, get user:1:", cache.get("user:1"))
print("After expiry, exists user:1:", cache.exists("user:1"))
print("TTL user:2:", cache.ttl("user:2"))
print("Cache size:", len(cache))
Output
Get user:1: {'name': 'Alice'}
TLL user:1: 2
After expiry, get user:1: None
After expiry, exists user:1: False
TTL user:2: -1
Cache size: 1
How it works
The class wraps an OrderedDict to preserve insertion order and a parallel dict tracking expiry timestamps. set stores the current time plus TTL, while get lazily deletes expired keys on access. The _cleanup method scans for expired entries and removes them, keeping len and __repr__ accurate. ttl returns -2 for missing keys, -1 for no expiry, or remaining seconds as an integer. This mirrors Redis semantics while staying dependency-free with the standard library.
Common mistakes
- Using `time.sleep` in production code — block only in tests or demos
- Assuming `get` returns the actual value instead of None for missing/expired keys
- Forgetting to set a default TTL, so keys never expire when `ttl=None`
Variations
- Use `threading.Lock` or `asyncio.Lock` to make the cache thread-safe
- Replace `OrderedDict` with a plain `dict` for Python 3.7+ where insertion order is guaranteed
Real-world use cases
- Mocking Redis in unit tests without requiring a live server dependency.
- Caching database query results in a single-process Flask/FastAPI app.
- Storing short-lived session tokens or rate-limit counters in memory.
Sponsored
More from Caching & Redis
- Cache Asides in Python with a Read-Through Loader easy
- Cache Data in Redis with Python easy
- Cache Penetration Null Object Mock in Python medium
- Cache Stampede Prevention with SingleFlight in Python medium
- Cache Warming with Python: Preload Hot Keys easy
- Coalescing duplicate in-flight requests: one shared result for concurrent callers hard
Keep learning
Related tutorials and quizzes for this topic.