Data Schema with Pydantic¶
Using Pydantic Models¶
Version >= 3.0.0
Pydantic support was added in version 3.0.0 through the new schema adapter system.
Pydantic 2.x
This documentation uses Pydantic 2.x (2.0+). If you're using Pydantic 1.x, please refer to the Pydantic V2 migration guide.
APIFlask supports Pydantic models alongside marshmallow schemas. You can use Pydantic's type-hint based approach for defining data models and validation.
Basic Pydantic Usage¶
Here's how to define and use Pydantic models with APIFlask:
from enum import Enum
from typing import Optional
from pydantic import BaseModel, Field
from apiflask import APIFlask
app = APIFlask(__name__)
class PetCategory(str, Enum):
dog = 'dog'
cat = 'cat'
bird = 'bird'
fish = 'fish'
class PetIn(BaseModel):
name: str = Field(min_length=1, max_length=50, description='Pet name')
category: PetCategory = Field(description='Pet category')
age: Optional[int] = Field(default=None, ge=0, le=30, description='Pet age')
class PetOut(BaseModel):
id: int = Field(description='Pet ID')
name: str = Field(description='Pet name')
category: PetCategory = Field(description='Pet category')
age: Optional[int] = Field(default=None, description='Pet age')
@app.post('/pets')
@app.input(PetIn)
@app.output(PetOut, status_code=201)
def create_pet(json_data: PetIn):
# json_data is automatically validated and deserialized into a PetIn instance
new_pet = PetOut(
id=1,
name=json_data.name,
category=json_data.category,
age=json_data.age
)
return new_pet
Pydantic Model Features¶
Type Hints and Validation¶
Pydantic uses Python type hints for automatic validation:
from datetime import datetime
from typing import Optional
from pydantic import BaseModel, Field, EmailStr, HttpUrl
class UserIn(BaseModel):
username: str = Field(min_length=3, max_length=20)
email: EmailStr # Requires: pip install pydantic[email]
age: int = Field(ge=18, le=120)
website: Optional[HttpUrl] = None # Requires: pip install pydantic[email]
tags: list[str] = Field(default_factory=list)
is_active: bool = True
created_at: datetime = Field(default_factory=datetime.now)
Custom Validators¶
Pydantic 2.x provides @field_validator for field-level validation:
from pydantic import BaseModel, field_validator, Field, ValidationError
class ProductIn(BaseModel):
name: str = Field(min_length=1)
price: float = Field(gt=0)
category: str
@field_validator('name')
@classmethod
def name_must_be_alphanumeric(cls, v: str) -> str:
if not v.replace(' ', '').isalnum():
raise ValueError('Name must be alphanumeric')
return v.title()
@field_validator('category')
@classmethod
def validate_category(cls, v: str) -> str:
categories = ['electronics', 'clothing', 'books', 'home']
if v.lower() not in categories:
raise ValueError(f'Category must be one of {categories}')
return v.lower()
For more complex validation across multiple fields, use @model_validator:
from datetime import datetime
from pydantic import BaseModel, model_validator, Field
class DateRange(BaseModel):
start_date: datetime
end_date: datetime
min_duration_days: int = Field(default=1, ge=1)
@model_validator(mode='after')
def check_dates(self) -> 'DateRange':
if self.end_date <= self.start_date:
raise ValueError('end_date must be after start_date')
duration = (self.end_date - self.start_date).days
if duration < self.min_duration_days:
raise ValueError(f'Duration must be at least {self.min_duration_days} days')
return self
Nested Models¶
Pydantic supports nested models:
from pydantic import BaseModel
class Pet(BaseModel):
name: str
category: str
class Address(BaseModel):
street: str
city: str
country: str
postal_code: str
class User(BaseModel):
name: str
email: str
address: Address
pets: list[Pet]
Pydantic with Query Parameters¶
You can use Pydantic models for query parameters:
from typing import Optional
from pydantic import BaseModel, Field
class SearchQuery(BaseModel):
q: str = Field(description='Search query')
page: int = Field(default=1, ge=1, description='Page number')
per_page: int = Field(default=10, ge=1, le=100, description='Items per page')
sort_by: Optional[str] = Field(default=None, description='Sort field')
@app.get('/search')
@app.input(SearchQuery, location='query')
@app.output(SearchResultsOut)
def search(query_data):
# query_data contains validated query parameters
return perform_search(query_data)
OpenAPI Schema Generation¶
Pydantic models automatically generate comprehensive OpenAPI schemas:
from typing import Optional
from pydantic import BaseModel, Field, ConfigDict
from enum import Enum
class Priority(str, Enum):
low = 'low'
medium = 'medium'
high = 'high'
class Task(BaseModel):
model_config = ConfigDict(
json_schema_extra={
'examples': [
{
'title': 'Complete project',
'description': 'Finish the API documentation',
'priority': 'high',
'completed': False,
'tags': ['work', 'urgent']
}
]
}
)
title: str = Field(description='Task title', examples=['Complete project'])
description: Optional[str] = Field(default=None, description='Task description')
priority: Priority = Field(default=Priority.medium, description='Task priority')
completed: bool = Field(default=False, description='Task completion status')
tags: list[str] = Field(default_factory=list, description='Task tags')
The above model will generate detailed OpenAPI schema with proper types, descriptions, examples, and enum values.
Error Handling¶
Pydantic validation errors are automatically converted to APIFlask's standard error format:
# When validation fails, you'll get structured error responses like:
{
'message': 'Validation error',
'detail': {
'json': {
'age': ['ensure this value is greater than or equal to 0'],
'category': ['Category must be one of [\'dog\', \'cat\', \'bird\', \'fish\']']
}
}
}
Complete Example¶
Check out the complete Pydantic example application for more details, see the examples page for running the example application.