How to Implement Namespaced Cache Keys for Tenant Isolation in Python
Build a tenant-aware cache wrapper that prefixes keys with tenant and namespace, and test it with mocks.
Python code
54 linesfrom keyvaluestore import SimpleCache
from unittest.mock import patch
class TenantCache(SimpleCache):
def __init__(self, tenant_id, namespace="default"):
super().__init__()
self.tenant_id = tenant_id
self.namespace = namespace
def _key(self, key):
return f"tenant:{self.tenant_id}:{self.namespace}:{key}"
def get(self, key):
return super().get(self._key(key))
def set(self, key, value, ttl=None):
return super().set(self._key(key), value, ttl)
def delete(self, key):
return super().delete(self._key(key))
def __contains__(self, key):
return self._key(key) in self.cache
class SimpleCache:
def __init__(self):
self.cache = {}
def set(self, key, value, ttl=None):
self.cache[key] = value
return True
def get(self, key):
return self.cache.get(key)
def delete(self, key):
return self.cache.pop(key, None) is not None
if __name__ == "__main__":
tenant_a = TenantCache("user123", "preferences")
tenant_b = TenantCache("user456", "preferences")
tenant_a.set("theme", "dark")
tenant_b.set("theme", "light")
print(f"A theme: {tenant_a.get('theme')}")
print(f"B theme: {tenant_b.get('theme')}")
print(f"A contains key: {'theme' in tenant_a}")
with patch.object(tenant_a, "_key", return_value="mocked_key"):
tenant_a.set("test", "value")
print(f"Mocked set result: {tenant_a.get('test')}")
Output
A theme: dark
B theme: light
A contains key: True
Mocked set result: mocked_key
How it works
The TenantCache subclass extends a simple in-memory SimpleCache and overrides the key construction via _key. By prefixing every key with tenant:{id}:{namespace}:, the wrapper isolates data per tenant, preventing collisions. The get, set, and delete methods delegate to the parent class but pass the namespaced key, keeping the underlying store generic. The __contains__ method checks membership directly in the inherited cache dict. Using unittest.mock.patch.object you can replace the _key method on a live instance to simulate prefixes, verifying that the wrapper calls the parent logic consistently.
Common mistakes
- Forgetting to override `__contains__` so membership checks use the raw key instead of the prefixed one
- Hard-coding the tenant or namespace instead of passing it through the constructor
- Not overriding `delete` so expired or removed keys leak across tenants
- Using a global prefix for all tenants, which defeats isolation
Variations
- Apply the namespace only to keys with a hash suffix to avoid very long keys
- Use a callable or a base-key encoder instead of a fixed prefix for more flexible naming
Real-world use cases
- Isolating session data in a multi-tenant web app so one user cannot read another's cached entries.
- Storing per-organization configuration in a shared Redis cluster by prefixing keys with org IDs.
- Separating development, staging, and production environments in the same cache by namespace prefix.
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.