Data Models
Pydantic Models
Basic Models
from pydantic import BaseModel
from typing import Optional
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
class User(BaseModel):
id: int
username: str
email: str
full_name: Optional[str] = None
disabled: Optional[bool] = None
Model Validation
from pydantic import BaseModel, Field, validator
from typing import Optional
class Item(BaseModel):
name: str = Field(..., min_length=1, max_length=100)
description: Optional[str] = Field(None, max_length=500)
price: float = Field(..., gt=0, description="Price must be greater than 0")
tax: Optional[float] = Field(None, ge=0, le=100)
@validator('name')
def name_must_contain_space(cls, v):
if ' ' not in v:
raise ValueError('name must contain a space')
return v.title()
@validator('price')
def price_must_be_positive(cls, v):
if v <= 0:
raise ValueError('price must be positive')
return v
Advanced Field Types
from pydantic import BaseModel, Field, EmailStr, HttpUrl, UUID4
from typing import List, Dict, Optional
from datetime import datetime
from enum import Enum
class StatusEnum(str, Enum):
active = "active"
inactive = "inactive"
pending = "pending"
class User(BaseModel):
id: UUID4
username: str = Field(..., min_length=3, max_length=20)
email: EmailStr
website: Optional[HttpUrl] = None
status: StatusEnum = StatusEnum.active
created_at: datetime = Field(default_factory=datetime.now)
tags: List[str] = Field(default_factory=list)
metadata: Dict[str, str] = Field(default_factory=dict)
Model Inheritance
Base Models
from pydantic import BaseModel
from typing import Optional
class BaseItem(BaseModel):
name: str
description: Optional[str] = None
class Item(BaseItem):
price: float
tax: Optional[float] = None
class ItemInDB(Item):
id: int
owner_id: int
Mixins
from pydantic import BaseModel
from datetime import datetime
class TimestampMixin(BaseModel):
created_at: datetime = Field(default_factory=datetime.now)
updated_at: Optional[datetime] = None
class Item(BaseModel, TimestampMixin):
name: str
price: float
Model Configuration
Config Class
from pydantic import BaseModel
class Item(BaseModel):
name: str
price: float
class Config:
orm_mode = True
allow_population_by_field_name = True
validate_assignment = True
use_enum_values = True
json_encoders = {
datetime: lambda v: v.isoformat()
}
Field Aliases
from pydantic import BaseModel, Field
class Item(BaseModel):
name: str
item_price: float = Field(alias="price")
tax_rate: float = Field(alias="tax")
class Config:
allow_population_by_field_name = True
Nested Models
Basic Nesting
from pydantic import BaseModel
from typing import List, Optional
class Address(BaseModel):
street: str
city: str
state: str
zip_code: str
class User(BaseModel):
username: str
email: str
address: Optional[Address] = None
class Order(BaseModel):
id: int
user: User
items: List[Item]
total: float
Self-Referencing Models
from pydantic import BaseModel
from typing import List, Optional
class Category(BaseModel):
id: int
name: str
parent: Optional['Category'] = None
children: List['Category'] = []
Category.model_rebuild() # Required for self-referencing
Model Serialization
Model Export
item = Item(name="Laptop", price=999.99, tax=99.99)
# To dict
item_dict = item.dict()
item_dict_exclude = item.dict(exclude={"tax"})
item_dict_include = item.dict(include={"name", "price"})
# To JSON
item_json = item.json()
item_json_exclude = item.json(exclude={"tax"})
Custom Serialization
from pydantic import BaseModel, Field
from typing import Optional
class Item(BaseModel):
name: str
price: float
description: Optional[str] = None
def dict(self, **kwargs):
data = super().dict(**kwargs)
# Custom serialization logic
data['formatted_price'] = f"${data['price']:.2f}"
return data
Model Validation
Root Validators
from pydantic import BaseModel, root_validator
class Item(BaseModel):
name: str
price: float
discount: Optional[float] = None
final_price: Optional[float] = None
@root_validator
def validate_final_price(cls, values):
price = values.get('price')
discount = values.get('discount')
if price and discount:
values['final_price'] = price - (price * discount / 100)
return values
Pre and Post Validators
from pydantic import BaseModel, validator
class Item(BaseModel):
name: str
price: float
@validator('name', pre=True)
def name_must_be_string(cls, v):
return str(v)
@validator('price', pre=True)
def parse_price(cls, v):
if isinstance(v, str):
return float(v.replace('$', ''))
return v
Dynamic Models
Model Factory
from pydantic import BaseModel, create_model
from typing import Optional
def create_item_model(include_tax: bool = True):
fields = {
'name': (str, ...),
'price': (float, ...),
'description': (Optional[str], None)
}
if include_tax:
fields['tax'] = (Optional[float], None)
return create_model('Item', **fields)
ItemWithTax = create_item_model(include_tax=True)
ItemWithoutTax = create_item_model(include_tax=False)
Runtime Model Creation
from pydantic import BaseModel
from typing import get_type_hints
def create_model_from_dict(model_name: str, field_definitions: dict):
class_dict = {'__annotations__': field_definitions}
return type(model_name, (BaseModel,), class_dict)
# Usage
fields = {
'name': str,
'price': float,
'description': Optional[str]
}
DynamicItem = create_model_from_dict('DynamicItem', fields)
Model Utilities
Model Copying
item = Item(name="Laptop", price=999.99)
# Copy with changes
updated_item = item.copy(update={"price": 1299.99})
# Deep copy
import copy
copied_item = copy.deepcopy(item)
Model Comparison
item1 = Item(name="Laptop", price=999.99)
item2 = Item(name="Laptop", price=999.99)
# Equality
print(item1 == item2) # True
# Difference
from pydantic import BaseModel
class Item(BaseModel):
name: str
price: float
def diff(self, other):
return {
field: (getattr(self, field), getattr(other, field))
for field in self.__fields__
if getattr(self, field) != getattr(other, field)
}
Model Integration with FastAPI
Request/Response Models
from fastapi import FastAPI
app = FastAPI()
class ItemCreate(BaseModel):
name: str
price: float
description: Optional[str] = None
class ItemResponse(BaseModel):
id: int
name: str
price: float
created_at: datetime
@app.post("/items/", response_model=ItemResponse)
async def create_item(item: ItemCreate):
# Process item
return ItemResponse(
id=1,
name=item.name,
price=item.price,
created_at=datetime.now()
)
Model Validation in Endpoints
from fastapi import HTTPException
@app.post("/items/")
async def create_item(item: ItemCreate):
try:
# Validate additional business logic
if item.price < 0:
raise ValueError("Price cannot be negative")
# Process item
return {"message": "Item created successfully"}
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))