Skip to main content

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))