How to Mock the Ambassador Pattern Retry Client in Python

This code demonstrates the ambassador pattern for API clients by simulating a flaky request and retrying with exponential backoff, useful for testing resilience in system design.

Easy Python 3.9+ Aug 9, 2026 System design patterns 15 views 0 copies

Python code

38 lines
Python 3.9+
import time
import random


class RetryingClient:
    """Retry wrapper simulating a flaky ambassador-style API client."""

    def __init__(self, max_attempts=3, base_delay=0.1):
        self.max_attempts = max_attempts
        self.base_delay = base_delay
        self.attempts = 0

    def _flaky_request(self):
        """Simulate a request that fails ~50% of the time."""
        self.attempts += 1
        if random.random() < 0.5:
            raise ConnectionError("Temporary network failure")
        return {"status": "ok", "attempt": self.attempts}

    def call(self):
        """Retry the flaky request with exponential backoff."""
        last_error = None
        for attempt in range(self.max_attempts):
            try:
                return self._flaky_request()
            except ConnectionError as exc:
                last_error = exc
                time.sleep(self.base_delay * (2 ** attempt))
        raise last_error


if __name__ == "__main__":
    client = RetryingClient(max_attempts=4)
    try:
        result = client.call()
        print(f"Success on attempt {result['attempt']}: {result}")
    except ConnectionError:
        print(f"Failed after {client.attempts} attempts")

Output

stdout
Success on attempt 2: {'status': 'ok', 'attempt': 2}
or
Failed after 4 attempts
(Output depends on randomness but typically shows either a success with an attempt number 1-4 or failure after 4 attempts)

How it works

The RetryingClient wraps a flaky API call in a retry loop with exponential backoff, mimicking the ambassador pattern where a sidecar handles retries. _flaky_request simulates failure by raising ConnectionError about 50% of the time. The call method catches that error, sleeps with a delay that doubles each attempt, and repeats until max attempts. This isolates retry logic from the calling code, making it testable as a mock for production resilience.

Common mistakes

  • Not resetting the attempt counter between calls, causing inaccurate reporting
  • Skipping sleep in tests to speed up execution, but then missing backoff logic verification
  • Assuming the retry always succeeds, but it can fail if all attempts hit the error

Variations

  1. Use `tenacity` library with `@retry` decorator instead of custom loop
  2. Implement with `itertools.count` and `try/except` with logging for each attempt

Real-world use cases

  • Simulating a flaky third-party payment API in unit tests to verify retry behavior without real network calls.
  • Mocking an ambassador sidecar that retries requests to a backend service during load testing.
  • Injecting a retry wrapper into a service client to handle transient network errors in production microservice calls.

Sponsored

Run this sample

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

Open editor

More from System design patterns

Related tutorials and quizzes for this topic.