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.

Easy Python 3.9+ Aug 9, 2026 Caching & Redis 17 views 0 copies

Python code

54 lines
Python 3.9+
from 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

stdout
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

  1. Apply the namespace only to keys with a hash suffix to avoid very long keys
  2. 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

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Caching & Redis

Related tutorials and quizzes for this topic.