Nested Models & Relationships
Composing models inside models for complex, hierarchical data structures.
So far all my models have had simple fields — strings, ints, bools. What if a field is itself a complex object, like an address inside a user?
You use a Pydantic model as the field type. Pydantic validates the nested model automatically:
from pydantic import BaseModel
class Address(BaseModel):
street: str
city: str
zip_code: str
class User(BaseModel):
name: str
email: str
address: Address
user = User(
name="Alice",
email="alice@example.com",
address={"street": "123 Main St", "city": "Portland", "zip_code": "97201"}
)
print(user.address.city) # "Portland"
Notice you can pass the address as a plain dictionary — Pydantic automatically converts it to an Address instance and validates all its fields.
What if the nested model has its own validators? Do those run too?
Yes. When Pydantic validates User, it validates Address first — including all its field constraints and @field_validator methods. Validation flows from the inside out:
from pydantic import BaseModel, Field, field_validator
class Address(BaseModel):
street: str = Field(min_length=1)
city: str = Field(min_length=1)
zip_code: str
@field_validator("zip_code")
@classmethod
def validate_zip(cls, v):
if not v.isdigit() or len(v) != 5:
raise ValueError("must be 5 digits")
return v
class User(BaseModel):
name: str
address: Address
If the zip code is wrong, the error message tells you exactly where: address.zip_code.
Can I have a list of nested models? Like a user with multiple addresses?
Absolutely. Use list[Address] as the type:
class Customer(BaseModel):
name: str
addresses: list[Address]
customer = Customer(
name="Bob",
addresses=[
{"street": "123 Main St", "city": "Portland", "zip_code": "97201"},
{"street": "456 Oak Ave", "city": "Seattle", "zip_code": "98101"}
]
)
print(len(customer.addresses)) # 2
print(customer.addresses[0].city) # "Portland"
Each dictionary in the list gets validated as an Address. If any one of them fails, Pydantic tells you which index had the problem.
How deep can the nesting go? Can I have models inside models inside models?
As deep as you need. Each level validates independently:
class GeoPoint(BaseModel):
lat: float = Field(ge=-90, le=90)
lng: float = Field(ge=-180, le=180)
class Location(BaseModel):
name: str
coordinates: GeoPoint
class Store(BaseModel):
brand: str
location: Location
store = Store(
brand="Acme",
location={
"name": "Downtown",
"coordinates": {"lat": 45.5, "lng": -122.6}
}
)
print(store.location.coordinates.lat) # 45.5
The key insight: break complex data into small, focused models, then compose them. Each model validates its own piece, and the whole structure is guaranteed correct.
Practice your skills
Sign up to write and run code in this lesson.
Nested Models & Relationships
Composing models inside models for complex, hierarchical data structures.
So far all my models have had simple fields — strings, ints, bools. What if a field is itself a complex object, like an address inside a user?
You use a Pydantic model as the field type. Pydantic validates the nested model automatically:
from pydantic import BaseModel
class Address(BaseModel):
street: str
city: str
zip_code: str
class User(BaseModel):
name: str
email: str
address: Address
user = User(
name="Alice",
email="alice@example.com",
address={"street": "123 Main St", "city": "Portland", "zip_code": "97201"}
)
print(user.address.city) # "Portland"
Notice you can pass the address as a plain dictionary — Pydantic automatically converts it to an Address instance and validates all its fields.
What if the nested model has its own validators? Do those run too?
Yes. When Pydantic validates User, it validates Address first — including all its field constraints and @field_validator methods. Validation flows from the inside out:
from pydantic import BaseModel, Field, field_validator
class Address(BaseModel):
street: str = Field(min_length=1)
city: str = Field(min_length=1)
zip_code: str
@field_validator("zip_code")
@classmethod
def validate_zip(cls, v):
if not v.isdigit() or len(v) != 5:
raise ValueError("must be 5 digits")
return v
class User(BaseModel):
name: str
address: Address
If the zip code is wrong, the error message tells you exactly where: address.zip_code.
Can I have a list of nested models? Like a user with multiple addresses?
Absolutely. Use list[Address] as the type:
class Customer(BaseModel):
name: str
addresses: list[Address]
customer = Customer(
name="Bob",
addresses=[
{"street": "123 Main St", "city": "Portland", "zip_code": "97201"},
{"street": "456 Oak Ave", "city": "Seattle", "zip_code": "98101"}
]
)
print(len(customer.addresses)) # 2
print(customer.addresses[0].city) # "Portland"
Each dictionary in the list gets validated as an Address. If any one of them fails, Pydantic tells you which index had the problem.
How deep can the nesting go? Can I have models inside models inside models?
As deep as you need. Each level validates independently:
class GeoPoint(BaseModel):
lat: float = Field(ge=-90, le=90)
lng: float = Field(ge=-180, le=180)
class Location(BaseModel):
name: str
coordinates: GeoPoint
class Store(BaseModel):
brand: str
location: Location
store = Store(
brand="Acme",
location={
"name": "Downtown",
"coordinates": {"lat": 45.5, "lng": -122.6}
}
)
print(store.location.coordinates.lat) # 45.5
The key insight: break complex data into small, focused models, then compose them. Each model validates its own piece, and the whole structure is guaranteed correct.
Practice your skills
Sign up to write and run code in this lesson.