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": "[email protected]",
"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 [email protected]. I’d greatly appreciate your feedback! :)