Skip to main content

FastAPI Model Config

When building APIs with FastAPI, Pydantic models are a cornerstone for data validation and serialization. The Config class nested within these models allows you to fine-tune how your models behave, affecting validation, serialization, and schema generation. This guide explains the Config class and how to customize it for your needs.

Introduction to Model Config​

Every Pydantic model in FastAPI can include a nested Config class that controls the model's behavior. This powerful feature lets you customize validation rules, JSON schema settings, ORM integration, and more.

Here's a simple example of a Pydantic model with a Config class:

python
from pydantic import BaseModel

class User(BaseModel):
name: str
email: str
password: str

class Config:
# Configuration settings go here
extra = "forbid" # Disallow extra fields
validate_assignment = True # Validate data when assigning attributes after creation

Common Config Options​

Let's explore the most commonly used configuration options:

Extra Fields Behavior​

The extra attribute controls how a model handles fields that aren't defined in the model:

python
from pydantic import BaseModel
from fastapi import FastAPI

app = FastAPI()

class UserStrict(BaseModel):
name: str

class Config:
extra = "forbid" # Raise error for extra fields

class UserFlexible(BaseModel):
name: str

class Config:
extra = "ignore" # Ignore extra fields

class UserCollect(BaseModel):
name: str

class Config:
extra = "allow" # Allow and include extra fields

@app.post("/strict-user/")
def create_strict_user(user: UserStrict):
return user

@app.post("/flexible-user/")
def create_flexible_user(user: UserFlexible):
return user

@app.post("/collect-user/")
def create_collect_user(user: UserCollect):
return user

Input for /strict-user/:

json
{
"name": "John",
"age": 30
}

Output:

json
{
"detail": [
{
"loc": ["body", "age"],
"msg": "extra fields not permitted",
"type": "value_error.extra"
}
]
}

Input for /flexible-user/:

json
{
"name": "John",
"age": 30
}

Output:

json
{
"name": "John"
}

Input for /collect-user/:

json
{
"name": "John",
"age": 30
}

Output:

json
{
"name": "John",
"age": 30
}

Schema Customization​

Customize how your model appears in the JSON Schema and OpenAPI documentation:

python
from pydantic import BaseModel, Field
from fastapi import FastAPI

app = FastAPI()

class UserProfile(BaseModel):
name: str
bio: str = Field(default="")

class Config:
title = "User Profile Schema"
description = "A model representing a user's public profile"
schema_extra = {
"example": {
"name": "Jane Doe",
"bio": "Full-stack developer with 5 years of experience"
}
}

@app.post("/users/")
def create_user(user: UserProfile):
return user

This configuration enhances your API documentation with clearer titles, descriptions, and example values.

Validation Behavior​

Control how and when validation occurs:

python
from pydantic import BaseModel, validator
from fastapi import FastAPI

app = FastAPI()

class Product(BaseModel):
name: str
price: float

@validator('price')
def price_must_be_positive(cls, v):
if v <= 0:
raise ValueError('Price must be positive')
return v

class Config:
validate_assignment = True # Validate when attributes are assigned
validate_all = True # Run validation even on fields not explicitly set

@app.get("/product-example")
def get_product():
# This will be validated at assignment time due to validate_assignment=True
product = Product(name="Laptop", price=999.99)

try:
# This will fail validation
product.price = -10
except ValueError as e:
return {"error": str(e)}

return product

Output:

json
{
"error": "Price must be positive"
}

ORM Mode​

When working with databases, ORM mode allows Pydantic to work more efficiently with ORMs like SQLAlchemy:

python
from typing import List, Optional
from fastapi import FastAPI, Depends
from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import Session, sessionmaker
from pydantic import BaseModel

app = FastAPI()

# SQLAlchemy setup
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(SQLALCHEMY_DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()

# SQLAlchemy model
class UserDB(Base):
__tablename__ = "users"

id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
email = Column(String, unique=True, index=True)

# Pydantic models
class UserBase(BaseModel):
name: str
email: str

class UserCreate(UserBase):
pass

class User(UserBase):
id: int

class Config:
orm_mode = True # Enable ORM mode

# Database dependency
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()

# Create tables
Base.metadata.create_all(bind=engine)

@app.post("/users/", response_model=User)
def create_user(user: UserCreate, db: Session = Depends(get_db)):
db_user = UserDB(name=user.name, email=user.email)
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user # ORM mode automatically converts this to User model

The orm_mode = True setting allows Pydantic to access attributes from the ORM model rather than assuming a dictionary structure, making it seamless to work with database models.

Additional Configuration Options​

Here are some other useful config options:

Alias Generation​

Control how field names are represented in JSON:

python
from pydantic import BaseModel
from fastapi import FastAPI

app = FastAPI()

class UserModel(BaseModel):
user_id: int
first_name: str
last_name: str

class Config:
alias_generator = lambda field_name: field_name.upper()
allow_population_by_field_name = True

@app.post("/users/")
def create_user(user: UserModel):
return {
"model_fields": {
"user_id": user.user_id,
"first_name": user.first_name,
"last_name": user.last_name
},
"json_fields": user.dict(by_alias=True)
}

Input:

json
{
"USER_ID": 123,
"FIRST_NAME": "John",
"LAST_NAME": "Doe"
}

Output:

json
{
"model_fields": {
"user_id": 123,
"first_name": "John",
"last_name": "Doe"
},
"json_fields": {
"USER_ID": 123,
"FIRST_NAME": "John",
"LAST_NAME": "Doe"
}
}

Custom Types and JSON Encoding​

Configure how custom data types are encoded to JSON:

python
import datetime
from pydantic import BaseModel
from fastapi import FastAPI

app = FastAPI()

class Event(BaseModel):
title: str
timestamp: datetime.datetime

class Config:
json_encoders = {
datetime.datetime: lambda dt: dt.strftime("%Y-%m-%d %H:%M:%S")
}

@app.get("/current-event")
def get_current_event():
return Event(
title="Conference",
timestamp=datetime.datetime.now()
)

Output:

json
{
"title": "Conference",
"timestamp": "2023-07-15 14:30:22"
}

Real-World Application: Advanced API Configuration​

Let's build a more complete example showing how Config can be used in a real-world API:

python
from typing import List, Optional, Dict, Any
from datetime import datetime
from pydantic import BaseModel, Field, validator
from fastapi import FastAPI, HTTPException

app = FastAPI()

class Address(BaseModel):
street: str
city: str
country: str
postal_code: str

class Config:
validate_assignment = True
schema_extra = {
"example": {
"street": "123 Main St",
"city": "New York",
"country": "USA",
"postal_code": "10001"
}
}

class User(BaseModel):
id: Optional[int] = None
username: str
email: str
full_name: str
disabled: bool = False
address: Optional[Address] = None
created_at: datetime = Field(default_factory=datetime.now)

@validator('email')
def email_must_contain_at(cls, v):
if '@' not in v:
raise ValueError('email must contain @')
return v

class Config:
title = "User Model"
extra = "forbid"
validate_assignment = True
allow_mutation = True # Whether the model is mutable after creation
json_encoders = {
datetime: lambda dt: dt.isoformat()
}
schema_extra = {
"example": {
"username": "johndoe",
"email": "john@example.com",
"full_name": "John Doe",
"disabled": False
}
}

# Mock database
db: Dict[int, User] = {}
next_id = 1

@app.post("/users/", response_model=User)
def create_user(user: User):
global next_id
user.id = next_id
db[next_id] = user
next_id += 1
return user

@app.get("/users/{user_id}", response_model=User)
def get_user(user_id: int):
if user_id not in db:
raise HTTPException(status_code=404, detail="User not found")
return db[user_id]

@app.get("/users/", response_model=List[User])
def list_users():
return list(db.values())

@app.put("/users/{user_id}", response_model=User)
def update_user(user_id: int, updated_user: User):
if user_id not in db:
raise HTTPException(status_code=404, detail="User not found")

# Keep original ID and created_at
updated_user.id = user_id
updated_user.created_at = db[user_id].created_at

db[user_id] = updated_user
return updated_user

This example demonstrates:

  1. Nested models with their own configurations
  2. Custom validators with model behavior control
  3. JSON encoding for datetime objects
  4. Schema examples to enhance documentation
  5. Immutability control through allow_mutation

Summary​

The Pydantic Config class provides powerful customization options for your FastAPI models. The main configuration options include:

  • Extra fields handling: extra = "forbid", "ignore", or "allow"
  • Validation behavior: validate_assignment, validate_all
  • ORM integration: orm_mode = True
  • Schema customization: title, description, schema_extra
  • JSON serialization: json_encoders
  • Field naming: alias_generator, allow_population_by_field_name

By mastering these configuration options, you can fine-tune how your models behave, improving API reliability, documentation quality, and development experience.

Additional Resources​

Exercises​

  1. Create a model for a blog post with Config settings that enforce strict validation and provide example data in the documentation.
  2. Implement a Pydantic model with custom field aliases to handle data from an external API that uses snake_case while your API uses camelCase.
  3. Build a user registration model with custom validators and appropriate Config settings to handle sensitive data correctly.
  4. Create a model for a database entity with ORM mode enabled, demonstrating integration with SQLAlchemy.


If you spot any mistakes on this website, please let me know at feedback@compilenrun.com. I’d greatly appreciate your feedback! :)