How to Build an Adapter to Translate External API Responses in Python

Build an adapter class that translates a mock external API's response shape into your internal representation, keeping callers decoupled from the external contract.

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

Python code

34 lines
Python 3.9+
import json
from typing import Dict, Any


class ExternalAPI:
    """Mock external service returning a different data shape."""
    def get_user(self, user_id: int) -> Dict[str, Any]:
        return {
            "id": user_id,
            "full_name": "Jane Doe",
            "email_address": "jane@example.com",
            "phn": "555-1234"
        }


class UserAdapter:
    """Adapts ExternalAPI response to the expected internal format."""
    def __init__(self, external_api: ExternalAPI):
        self._api = external_api

    def get_user(self, user_id: int) -> Dict[str, Any]:
        raw = self._api.get_user(user_id)
        return {
            "user_id": raw["id"],
            "name": raw["full_name"],
            "email": raw["email_address"],
            "phone": raw["phn"]
        }


if __name__ == "__main__":
    adapter = UserAdapter(ExternalAPI())
    result = adapter.get_user(101)
    print(json.dumps(result, indent=2))

Output

stdout
{
  "user_id": 101,
  "name": "Jane Doe",
  "email": "jane@example.com",
  "phone": "555-1234"
}

How it works

The adapter pattern shields internal code from external API changes. UserAdapter wraps the ExternalAPI instance and exposes a clean, domain-friendly get_user method. Each field from the raw dict is mapped to your preferred key names, so the rest of the application never sees full_name or phn. The adapter's constructor takes the external API as a dependency, which makes it easy to swap in fakes or mock implementations during testing. This single translation point centralizes any field renaming or data shape normalization.

Common mistakes

  • Hardcoding the external API dependency inside the adapter instead of injecting it via the constructor
  • Returning the raw dict directly when the internal format diverges from the external shape
  • Assuming keys exist without validating or using .get() for defensive access

Variations

  1. Use a generic function `map_external_user(raw)` instead of a class when no state is needed.
  2. Add `.get()` with default values to handle missing or optional fields gracefully.

Real-world use cases

  • Wrapping a third-party CRM API so your models use consistent field names regardless of vendor changes.
  • Normalizing responses from multiple payment gateways into one internal transaction schema.
  • Isolating a legacy service response format behind an adapter during a slow migration to a new API.

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.