StructuralPythonverifiedVerified
Adapter Pattern in Python
Converts the interface of a class into another interface that clients expect, allowing incompatible interfaces to work together.
How to Implement the Adapter Pattern in Python
1Step 1: Define the target interface and the incompatible adaptee
from typing import Protocol
class Target(Protocol):
def request(self) -> str: ...
class Adaptee:
def specific_request(self) -> str:
return "Adaptee: specific behaviour"2Step 2: Implement the adapter that bridges both interfaces
class Adapter:
def __init__(self, adaptee: Adaptee) -> None:
self._adaptee = adaptee
def request(self) -> str:
result = self._adaptee.specific_request()[::-1]
return f"Adapter: (translated) {result}"3Step 3: Use the adapter transparently through the target interface
def client_code(target: Target) -> None:
print(target.request())
adaptee = Adaptee()
print("Adaptee:", adaptee.specific_request())
adapter = Adapter(adaptee)
client_code(adapter)"""API version adapter normalizing v1 and v2 user responses."""
from dataclasses import dataclass
from typing import Any, Protocol
# [step] Define the canonical user model and adapter protocol
@dataclass(frozen=True)
class User:
id: str
full_name: str
email: str
avatar_url: str | None
class UserAdapter(Protocol):
def adapt(self, raw: dict[str, Any]) -> User: ...
# [step] Implement V1 adapter for legacy snake_case API
class V1UserAdapter:
def adapt(self, raw: dict[str, Any]) -> User:
return User(
id=str(raw["userId"]),
full_name=f'{raw["first_name"]} {raw["last_name"]}'.strip(),
email=raw["email_address"],
avatar_url=raw.get("profile_pic"),
)
# [step] Implement V2 adapter for modern camelCase API
class V2UserAdapter:
def adapt(self, raw: dict[str, Any]) -> User:
avatar = raw.get("avatar")
return User(
id=raw["uuid"],
full_name=raw["displayName"],
email=raw["contactEmail"],
avatar_url=avatar["url"] if avatar else None,
)
# [step] Factory function for runtime adapter selection
def adapt_user(raw: dict[str, Any], version: str) -> User:
adapters: dict[str, UserAdapter] = {
"v1": V1UserAdapter(),
"v2": V2UserAdapter(),
}
adapter = adapters.get(version)
if adapter is None:
raise ValueError(f'Unknown API version: "{version}"')
return adapter.adapt(raw)
# Usage
legacy = {
"userId": 42,
"first_name": "Jane",
"last_name": "Doe",
"email_address": "[email protected]",
"profile_pic": "https://cdn.example.com/jane.png",
}
modern = {
"uuid": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"displayName": "John Smith",
"contactEmail": "[email protected]",
"avatar": {"url": "https://cdn.example.com/john.png"},
}
print(adapt_user(legacy, "v1"))
print(adapt_user(modern, "v2"))Adapter Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Adapter Pattern in the Real World
“A travel power adapter lets your American laptop plug (client) work in a European wall socket (adaptee) without modifying either. The adapter speaks both “languages”, translating the two-pin plug to the two-round-pin socket, making them interoperable without any changes on either side.”