FastAPI Model Inheritance
Introduction
When building APIs with FastAPI, you'll often need to define multiple related data models. These models might share common fields or behaviors. Rather than duplicating code across models, Pydantic (FastAPI's data validation library) supports inheritance, allowing you to create base models that can be extended by other models.
In this tutorial, we'll explore how to leverage model inheritance in FastAPI to create clean, reusable, and maintainable code structures.
Understanding Pydantic Model Inheritance
Model inheritance in Pydantic works similarly to regular Python class inheritance. You can create a base model with common fields and methods, then extend it to create more specialized models.
Basic Inheritance Example
Let's start with a simple example:
from pydantic import BaseModel
from typing import Optional
# Base model with common fields
class UserBase(BaseModel):
email: str
is_active: bool = True
# Creating child models that inherit from UserBase
class UserCreate(UserBase):
password: str
class UserResponse(UserBase):
id: int
username: Optional[str] = None
In this example:
UserBase
defines the common fields that all user-related models will haveUserCreate
extendsUserBase
and adds a password field for user creationUserResponse
extendsUserBase
and adds an ID and optional username for API responses
Using these models in a FastAPI route would look like this:
from fastapi import FastAPI
app = FastAPI()
@app.post("/users/", response_model=UserResponse)
def create_user(user: UserCreate):
# In a real application, you would save to a database
# and hash the password
return {
"id": 1, # generated ID
"email": user.email,
"is_active": user.is_active,
"username": None
}
Advanced Inheritance Techniques
Config Inheritance
You can also inherit configuration settings from parent models:
class BaseModel(BaseModel):
class Config:
# Base configuration
extra = "forbid" # Reject extra fields
orm_mode = True # Allow ORM mode
class ChildModel(BaseModel):
# Inherits the Config from BaseModel
name: str
age: int
Multiple Inheritance
Pydantic supports multiple inheritance, allowing you to combine fields and behaviors from multiple parent models:
class TimestampMixin(BaseModel):
created_at: datetime
updated_at: Optional[datetime] = None
class AuditMixin(BaseModel):
created_by: str
updated_by: Optional[str] = None
class Product(TimestampMixin, AuditMixin):
id: int
name: str
price: float
description: Optional[str] = None
Here, Product
inherits fields from both TimestampMixin
and AuditMixin
.
Practical Use Cases
1. API Request/Response Models
One common pattern is to create a base model and then extend it for different API operations:
from typing import List, Optional
from pydantic import BaseModel
class ArticleBase(BaseModel):
title: str
content: str
published: bool = False
class ArticleCreate(ArticleBase):
# No additional fields, but we separate it for API clarity
pass
class ArticleUpdate(BaseModel):
title: Optional[str] = None
content: Optional[str] = None
published: Optional[bool] = None
class ArticleInDB(ArticleBase):
id: int
author_id: int
class ArticleResponse(ArticleInDB):
tags: List[str] = []
Using these models in FastAPI routes:
@app.post("/articles/", response_model=ArticleResponse)
def create_article(article: ArticleCreate, current_user_id: int = Depends(get_current_user)):
# Logic to create the article
new_article = {
**article.dict(),
"id": generate_id(),
"author_id": current_user_id,
"tags": []
}
return new_article
@app.patch("/articles/{article_id}", response_model=ArticleResponse)
def update_article(article_id: int, article_update: ArticleUpdate):
# Logic to update the article
updated_article = {
"id": article_id,
"author_id": 1,
"title": "Updated title" if article_update.title else "Original title",
"content": "Updated content" if article_update.content else "Original content",
"published": article_update.published if article_update.published is not None else False,
"tags": ["updated"]
}
return updated_article
2. Database Models and DTOs
When working with databases, you can create a hierarchy of models for different purposes:
from datetime import datetime
from typing import Optional, List
from pydantic import BaseModel, EmailStr
# Base user model
class UserBase(BaseModel):
email: EmailStr
name: str
is_active: bool = True
# For creating users
class UserCreate(UserBase):
password: str
# For database storage
class UserInDB(UserBase):
id: int
hashed_password: str
created_at: datetime
class Config:
orm_mode = True
# For API responses
class UserPublic(UserBase):
id: int
created_at: datetime
class Config:
orm_mode = True
# For including user posts
class Post(BaseModel):
id: int
title: str
content: str
class Config:
orm_mode = True
class UserWithPosts(UserPublic):
posts: List[Post] = []
Field Overriding
You can override fields from parent models in child models. This is useful when you need to change field types or add validation:
from pydantic import BaseModel, Field
class Parent(BaseModel):
name: str
age: int = Field(..., gt=0)
class Child(Parent):
# Override the age field to be more restrictive
age: int = Field(..., gt=0, lt=18)
Summary
Model inheritance in FastAPI's Pydantic models offers a powerful way to organize your data models and reduce code duplication. Key benefits include:
- Code Reuse: Define common fields and behaviors once
- Clear API Documentation: Create specific models for different API operations
- Flexibility: Override and extend models as needed
- Better Organization: Create logical hierarchies of related models
When building complex APIs, leveraging model inheritance helps maintain a clean and maintainable codebase while ensuring your data validation remains robust.
Exercises
- Create a hierarchy of models for an e-commerce system with products, categories, and orders
- Implement a blog API with models for posts, comments, and users using inheritance
- Extend the user models in this tutorial to include address information and profile settings
Additional Resources
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)