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.
Python code
34 linesimport 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
{
"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
- Use a generic function `map_external_user(raw)` instead of a class when no state is needed.
- 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
More from System design patterns
- Build a BFF (Backend for Frontend) Mock Aggregator in Python medium
- Builder pattern for mocking complex objects in Python easy
- Circuit Breaker Pattern in Python: Closed, Open, and Half-Open States medium
- Create a Data Helper Class in Python easy
- Domain Driven Design Aggregate Root Example in Python medium
- Facade Pattern in Python with Mock Simplification medium
Keep learning
Related tutorials and quizzes for this topic.