By default, to_pydantic treats foreign keys as flat scalar values (using the underlying column name, e.g., user_id). To embed the related object instead, use the relationships parameter.
Nested Foreign Keys
Pass a dictionary mapping the ForeignKeyField to the desired Pydantic schema.
# Include the id field in the response
UserSchema = to_pydantic(User, exclude_autofield=False)
# Embed the User object inside the Tweet response
TweetResponse = to_pydantic(
Tweet,
exclude_autofield=False,
relationships={Tweet.user: UserSchema}
)
# To avoid extra SELECT queries during validation, use a JOIN
tweet = (Tweet.select(Tweet, User).join(User).get())
data = TweetResponse.model_validate(tweet)
Nested Back-references
Back-references (e.g., User.tweets) can also be nested, but because they represent a collection, the schema must be wrapped in typing.List.
from typing import List
# Exclude the 'user' FK from Tweet to prevent circular nesting
TweetResponse = to_pydantic(Tweet, exclude={'user'}, exclude_autofield=False)
# Map the backref to a list of TweetResponse schemas
UserDetail = to_pydantic(
User,
exclude_autofield=False,
relationships={User.tweets: List[TweetResponse]}
)
# To avoid extra queries, use .with_related()
users = (User.select().where(User.id == 123).with_related(Load(User.tweets)))
data = UserDetail.model_validate(users[0])
Async Considerations
In async applications using the asyncio extension, lazy-loading a relation outside of db.run() will raise a MissingGreenletBridge. To safely validate models with unloaded relations, run the validation inside the bridge:
data = await db.run(UserDetail.model_validate, user)
# Nested foreign key example
UserSchema = to_pydantic(User, exclude_autofield=False)
TweetResponse = to_pydantic(
Tweet,
exclude_autofield=False,
relationships={Tweet.user: UserSchema})
tweet = Tweet.create(user=huey, content='hello')
data = TweetResponse.model_validate(tweet)
print(data.model_dump())
# {'id': 1, 'content': 'hello', 'user': {'id': 1, 'name': 'Huey', ...}, ...}