Fallback cached response mock in Python

Wraps a mock function with a fallback to a real service and caches results to mask transient failures.

Medium Python 3.9+ Aug 9, 2026 Microservices patterns 14 views 0 copies

Python code

51 lines
Python 3.9+
import time
from functools import wraps

class CachedMock:
    def __init__(self, cache_ttl=5):
        self.cache = {}
        self.cache_ttl = cache_ttl

    def get(self, key):
        cached = self.cache.get(key)
        if cached and time.time() - cached["timestamp"] < self.cache_ttl:
            return cached["value"]
        return None

    def set(self, key, value):
        self.cache[key] = {"value": value, "timestamp": time.time()}

def fallback_cached(mock_func, real_func, cache_ttl=5):
    cache = CachedMock(cache_ttl)

    @wraps(mock_func)
    def wrapper(*args, **kwargs):
        cache_key = f"{args}:{kwargs}"
        cached_value = cache.get(cache_key)
        if cached_value is not None:
            return cached_value

        try:
            value = mock_func(*args, **kwargs)
        except Exception:
            value = real_func(*args, **kwargs)

        cache.set(cache_key, value)
        return value

    return wrapper


def fake_mock(x):
    raise ConnectionError("Mock service down")

def real_service(x):
    return f"real:{x * 2}"


mock_with_fallback = fallback_cached(fake_mock, real_service, cache_ttl=3)

if __name__ == "__main__":
    print(mock_with_fallback(4))
    print(mock_with_fallback(4))
    print(mock_with_fallback(5))

Output

stdout
real:8
real:8
real:10

How it works

The decorator wraps a mock function and tries it first. If the mock raises an exception, it falls back to the real function. Results are cached by arguments so the mock can return instantly on repeated calls while the cache is valid. The cache uses timestamps to expire entries after a TTL, preventing stale data if the mock recovers or conditions change.

Common mistakes

  • Using the cache key without including kwargs, causing collisions
  • Caching exceptions instead of falling back and caching the real result
  • Forgetting to reset the cache when the underlying service changes
  • Not handling None as a valid cached value

Variations

  1. Use functools.lru_cache with a custom key for simpler caching
  2. Add circuit-breaker logic to disable the mock for a cooldown period after repeated failures

Real-world use cases

  • Testing microservices against mocked external APIs while keeping real fallbacks for integration.
  • Degrading gracefully in front of third-party APIs when the primary dependency temporarily fails.
  • Caching expensive mock responses in tests to speed up suite execution without hitting network calls.

Sponsored

Run this sample

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

Open editor

More from Microservices patterns

Related tutorials and quizzes for this topic.