PYTHON
Transforming External API Responses to Internal Data Models
Learn to map and transform complex external API data structures into simpler, normalized internal data models for better application logic and consistency using Python.
import requests
import json
# --- Simulated External API Response ---
# In a real scenario, this would come from requests.get('...')
raw_api_data = {
"id": "USR_12345",
"personal_info": {
"first_name": "Alice",
"last_name": "Smith",
"email_address": "[email protected]",
"contact_phone": "+1-555-123-4567"
},
"account_status": {
"is_active": True,
"joined_date": "2023-01-15T10:00:00Z"
},
"address_details": {
"street": "123 Main St",
"city": "Anytown",
"zip_code": "90210",
"country": "USA"
},
"preferences": [
{"type": "newsletter", "value": True},
{"type": "notifications", "value": "email"},
]
}
# --- Internal Data Model Definition (Simplified) ---
class UserProfile:
def __init__(self, user_id, first_name, last_name, email, phone, is_active, joined_at, address):
self.user_id = user_id
self.first_name = first_name
self.last_name = last_name
self.email = email
self.phone = phone
self.is_active = is_active
self.joined_at = joined_at
self.address = address # Can be another nested object/dict
def to_dict(self):
return {
"user_id": self.user_id,
"first_name": self.first_name,
"last_name": self.last_name,
"email": self.email,
"phone": self.phone,
"is_active": self.is_active,
"joined_at": self.joined_at,
"address": self.address
}
def __repr__(self):
return f"<UserProfile: {self.first_name} {self.last_name} ({self.user_id})>"
# --- Transformation Function ---
def transform_api_user_data(api_data: dict) -> UserProfile:
"""
Transforms raw API user data into a standardized UserProfile object.
"""
try:
user_id = api_data["id"]
first_name = api_data["personal_info"]["first_name"]
last_name = api_data["personal_info"]["last_name"]
email = api_data["personal_info"]["email_address"]
phone = api_data["personal_info"].get("contact_phone", "N/A") # Use .get for optional fields
is_active = api_data["account_status"]["is_active"]
joined_at = api_data["account_status"]["joined_date"]
address = {
"street": api_data["address_details"]["street"],
"city": api_data["address_details"]["city"],
"zip": api_data["address_details"]["zip_code"],
"country": api_data["address_details"]["country"]
}
# If you needed to process preferences, you'd do it here
# newsletter_pref = next((p['value'] for p in api_data['preferences'] if p['type'] == 'newsletter'), False)
return UserProfile(
user_id=user_id,
first_name=first_name,
last_name=last_name,
email=email,
phone=phone,
is_active=is_active,
joined_at=joined_at,
address=address
)
except KeyError as e:
raise ValueError(f"Missing expected field in API data: {e}")
except Exception as e:
raise RuntimeError(f"Error transforming API data: {e}")
# --- Usage Example ---
def get_and_transform_user(user_id: str):
# In a real app:
# response = requests.get(f"https://api.external.com/users/{user_id}")
# raw_data = response.json()
raw_data = raw_api_data # Using our simulated data for demonstration
user_profile = transform_api_user_data(raw_data)
print(f"Transformed User: {user_profile.to_dict()}")
return user_profile
if __name__ == "__main__":
transformed_user = get_and_transform_user("USR_12345")
# Access attributes
print(f"User ID: {transformed_user.user_id}")
print(f"Email: {transformed_user.email}")
print(f"Address City: {transformed_user.address['city']}")
# Example of handling a missing field gracefully
simulated_data_missing_phone = {**raw_api_data, "personal_info": {**raw_api_data["personal_info"], "contact_phone": None}}
simulated_data_missing_phone["personal_info"].pop("contact_phone") # Remove it entirely
try:
user_with_missing_phone = transform_api_user_data(simulated_data_missing_phone)
print(f"
User with missing phone (handled): {user_with_missing_phone.phone}")
except Exception as e:
print(f"
Error transforming data with missing phone: {e}")
# Example of handling a critical missing field
simulated_data_missing_email = {**raw_api_data, "personal_info": {**raw_api_data["personal_info"], "email_address": None}}
simulated_data_missing_email["personal_info"].pop("email_address")
try:
transform_api_user_data(simulated_data_missing_email)
except Exception as e:
print(f"
Error transforming data with critically missing email: {e}")
How it works: This Python snippet demonstrates a robust method for transforming data received from an external API into a standardized internal data model (`UserProfile` class). External APIs often return data with inconsistent naming conventions, nested structures, or irrelevant fields. By implementing a dedicated transformation function, developers can map, rename, and normalize API response fields, ensuring that the application works with a consistent and predictable data structure. This improves code readability, maintainability, and makes the application more resilient to changes in the external API's response format.