원클릭으로
pydantic
// Python data validation using type hints and runtime type checking with Pydantic v2's Rust-powered core for high-performance validation in FastAPI, Django, and configuration management.
// Python data validation using type hints and runtime type checking with Pydantic v2's Rust-powered core for high-performance validation in FastAPI, Django, and configuration management.
Python asyncio - Modern concurrent programming with async/await, event loops, tasks, coroutines, primitives, aiohttp, and FastAPI async patterns
mypy - Static type checker for Python with gradual typing, strict mode, Protocol support, and framework integration
Essential Git patterns for effective version control, eliminating redundant Git guidance per agent.
Use git worktrees for parallel development on multiple branches simultaneously
Create and manage stacked (dependent) pull requests for complex features
Safe patterns for evolving database schemas in production with decision trees and troubleshooting guidance.
| name | pydantic |
| description | Python data validation using type hints and runtime type checking with Pydantic v2's Rust-powered core for high-performance validation in FastAPI, Django, and configuration management. |
| progressive_disclosure | {"entry_point":["summary","when_to_use","quick_start"],"full_content":"all"} |
| token_estimates | {"entry_point":70,"full":5500} |
Python data validation using type hints and runtime type checking with Pydantic v2's Rust-powered core for high-performance validation.
from pydantic import BaseModel, Field, EmailStr
from datetime import datetime
class User(BaseModel):
id: int
name: str = Field(..., min_length=1, max_length=100)
email: EmailStr
created_at: datetime = Field(default_factory=datetime.now)
is_active: bool = True
# Validate data
user = User(id=1, name="Alice", email="alice@example.com")
print(user.model_dump()) # {'id': 1, 'name': 'Alice', ...}
# Automatic type coercion
user2 = User(id="2", name="Bob", email="bob@example.com")
assert user2.id == 2 # String "2" coerced to int
# Validation error
try:
User(id=3, name="", email="invalid")
except ValidationError as e:
print(e.errors())
from pydantic import BaseModel, ConfigDict
class Product(BaseModel):
model_config = ConfigDict(
str_strip_whitespace=True,
validate_assignment=True,
use_enum_values=True,
arbitrary_types_allowed=False
)
name: str
price: float
quantity: int = 0
# Usage
product = Product(name=" Widget ", price=19.99)
assert product.name == "Widget" # Whitespace stripped
# Validate on assignment
product.price = "29.99" # Auto-converts to float
from pydantic import Field, field_validator
from typing import Annotated
class Item(BaseModel):
# Field constraints
sku: str = Field(pattern=r'^[A-Z]{3}-\d{4}$')
price: float = Field(gt=0, le=10000)
stock: int = Field(ge=0, default=0)
# Annotated types (Pydantic v2)
quantity: Annotated[int, Field(ge=1, le=100)]
# Descriptions and examples
description: str = Field(
...,
description="Product description",
examples=["High-quality widget"]
)
# Deprecated fields
old_field: str | None = Field(None, deprecated=True)
@field_validator('sku')
@classmethod
def validate_sku(cls, v: str) -> str:
if not v.startswith('ABC'):
raise ValueError('SKU must start with ABC')
return v
# Pydantic v1
class OldModel(BaseModel):
class Config:
validate_assignment = True
json_encoders = {datetime: lambda v: v.isoformat()}
# Pydantic v2
class NewModel(BaseModel):
model_config = ConfigDict(
validate_assignment=True,
# json_encoders replaced by serializers
)
@model_serializer
def ser_model(self) -> dict:
return {...}
# Key changes:
# - .dict() → .model_dump()
# - .json() → .model_dump_json()
# - .parse_obj() → .model_validate()
# - .parse_raw() → .model_validate_json()
# - @validator → @field_validator
# - @root_validator → @model_validator
# v2 uses Rust core (pydantic-core) for 5-50x speedup
from pydantic import BaseModel
import time
class Data(BaseModel):
values: list[int]
names: list[str]
# Benchmark
data = {'values': list(range(10000)), 'names': ['item'] * 10000}
start = time.perf_counter()
for _ in range(1000):
Data.model_validate(data)
elapsed = time.perf_counter() - start
print(f"Validated 1000 iterations in {elapsed:.2f}s")
from pydantic import (
BaseModel, EmailStr, HttpUrl, UUID4,
FilePath, DirectoryPath, Json, SecretStr,
PositiveInt, NegativeFloat, conint, constr
)
from typing import Literal
from pathlib import Path
class Example(BaseModel):
# Email validation
email: EmailStr
# URL validation
website: HttpUrl
# UUID
id: UUID4
# File system paths
config_file: FilePath
data_dir: DirectoryPath
# JSON string → parsed object
metadata: Json[dict[str, str]]
# Secret (won't print in logs)
api_key: SecretStr
# Constrained types
age: PositiveInt
balance: NegativeFloat
username: constr(min_length=3, max_length=20, pattern=r'^[a-z]+$')
code: conint(ge=1000, le=9999)
# Literal types
status: Literal['pending', 'approved', 'rejected']
from pydantic import GetCoreSchemaHandler, GetJsonSchemaHandler
from pydantic_core import core_schema
from typing import Any
class Color:
def __init__(self, r: int, g: int, b: int):
self.r, self.g, self.b = r, g, b
@classmethod
def __get_pydantic_core_schema__(
cls, source_type: Any, handler: GetCoreSchemaHandler
) -> core_schema.CoreSchema:
return core_schema.no_info_after_validator_function(
cls.validate,
core_schema.str_schema()
)
@classmethod
def validate(cls, v: str) -> 'Color':
if not v.startswith('#') or len(v) != 7:
raise ValueError('Invalid hex color')
r = int(v[1:3], 16)
g = int(v[3:5], 16)
b = int(v[5:7], 16)
return cls(r, g, b)
class Design(BaseModel):
primary_color: Color
# Usage
design = Design(primary_color='#FF5733')
assert design.primary_color.r == 255
from pydantic import field_validator, model_validator
class Account(BaseModel):
username: str
password: str
password_confirm: str
@field_validator('username')
@classmethod
def username_alphanumeric(cls, v: str) -> str:
if not v.isalnum():
raise ValueError('must be alphanumeric')
return v
@field_validator('password')
@classmethod
def password_strong(cls, v: str) -> str:
if len(v) < 8:
raise ValueError('must be at least 8 characters')
if not any(c.isupper() for c in v):
raise ValueError('must contain uppercase letter')
return v
# Validate multiple fields
@field_validator('username', 'password')
@classmethod
def not_empty(cls, v: str) -> str:
if not v or not v.strip():
raise ValueError('must not be empty')
return v.strip()
from pydantic import model_validator
from typing import Self
class DateRange(BaseModel):
start_date: datetime
end_date: datetime
@model_validator(mode='after')
def check_dates(self) -> Self:
if self.end_date < self.start_date:
raise ValueError('end_date must be after start_date')
return self
class Order(BaseModel):
items: list[str]
total: float
discount: float = 0
@model_validator(mode='before')
@classmethod
def calculate_total(cls, data: dict) -> dict:
# Pre-processing before validation
if isinstance(data, dict) and 'total' not in data:
data['total'] = len(data.get('items', [])) * 10.0
return data
from pydantic import model_validator, ValidationInfo
class Config(BaseModel):
env: Literal['dev', 'prod']
debug: bool = False
@model_validator(mode='wrap')
@classmethod
def validate_config(cls, values: Any, handler, info: ValidationInfo):
# Call default validation
result = handler(values)
# Post-validation logic
if result.env == 'prod' and result.debug:
raise ValueError('debug cannot be True in production')
return result
from pydantic import BaseModel, ConfigDict, ValidationError
# Coercive mode (default)
class CoerciveModel(BaseModel):
count: int
price: float
data = CoerciveModel(count="42", price="19.99")
assert data.count == 42 # String → int
assert data.price == 19.99 # String → float
# Strict mode
class StrictModel(BaseModel):
model_config = ConfigDict(strict=True)
count: int
price: float
try:
StrictModel(count="42", price="19.99") # Raises ValidationError
except ValidationError as e:
print("Strict mode: no coercion allowed")
# Per-field strict mode
class MixedModel(BaseModel):
flexible: int # Allows coercion
strict: Annotated[int, Field(strict=True)] # No coercion
MixedModel(flexible="1", strict=2) # OK
# MixedModel(flexible="1", strict="2") # ValidationError
from pydantic import BaseModel
from typing import ForwardRef
# Nested models
class Address(BaseModel):
street: str
city: str
country: str
class Company(BaseModel):
name: str
address: Address
company = Company(
name="ACME Corp",
address={'street': '123 Main St', 'city': 'NYC', 'country': 'USA'}
)
# Recursive types (tree structure)
class TreeNode(BaseModel):
value: int
children: list['TreeNode'] = []
TreeNode.model_rebuild() # Required for forward references
tree = TreeNode(
value=1,
children=[
TreeNode(value=2, children=[TreeNode(value=4)]),
TreeNode(value=3)
]
)
# Self-referencing with ForwardRef
class Category(BaseModel):
name: str
parent: 'Category | None' = None
subcategories: list['Category'] = []
Category.model_rebuild()
from pydantic import BaseModel
from typing import Generic, TypeVar
T = TypeVar('T')
class Response(BaseModel, Generic[T]):
success: bool
data: T
message: str = ''
class User(BaseModel):
id: int
name: str
# Usage with concrete type
user_response = Response[User](
success=True,
data=User(id=1, name='Alice')
)
# List response
list_response = Response[list[User]](
success=True,
data=[User(id=1, name='Alice'), User(id=2, name='Bob')]
)
# Generic repository pattern
class Repository(BaseModel, Generic[T]):
items: list[T]
def add(self, item: T) -> None:
self.items.append(item)
user_repo = Repository[User](items=[])
user_repo.add(User(id=1, name='Alice'))
from pydantic import BaseModel, Field, field_serializer
class Article(BaseModel):
title: str
content: str
tags: list[str]
metadata: dict[str, Any] = {}
# Serialization customization
@field_serializer('tags')
def serialize_tags(self, tags: list[str]) -> str:
return ','.join(tags)
article = Article(
title='Pydantic Guide',
content='...',
tags=['python', 'validation']
)
# Dump to dict
data = article.model_dump()
# {'title': 'Pydantic Guide', 'tags': 'python,validation', ...}
# Exclude fields
data = article.model_dump(exclude={'metadata'})
# Include only specific fields
data = article.model_dump(include={'title', 'tags'})
# Exclude unset fields
article2 = Article(title='Test', content='...', tags=[])
data = article2.model_dump(exclude_unset=True) # metadata excluded
# By alias
class AliasModel(BaseModel):
internal_name: str = Field(alias='externalName')
model = AliasModel(externalName='value')
model.model_dump(by_alias=True) # {'externalName': 'value'}
from datetime import datetime
from pydantic import BaseModel, field_serializer
class Event(BaseModel):
name: str
timestamp: datetime
@field_serializer('timestamp')
def serialize_dt(self, dt: datetime) -> str:
return dt.isoformat()
event = Event(name='Deploy', timestamp=datetime.now())
# Dump to JSON string
json_str = event.model_dump_json()
# '{"name":"Deploy","timestamp":"2025-11-30T..."}'
# Pretty print
json_str = event.model_dump_json(indent=2)
# Parse from JSON
event2 = Event.model_validate_json(json_str)
from pydantic import model_serializer
class User(BaseModel):
id: int
username: str
password: SecretStr
@model_serializer
def ser_model(self) -> dict[str, Any]:
return {
'id': self.id,
'username': self.username,
# Never serialize password
}
user = User(id=1, username='alice', password='secret123')
assert 'password' not in user.model_dump()
from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic import Field
class AppSettings(BaseSettings):
model_config = SettingsConfigDict(
env_file='.env',
env_file_encoding='utf-8',
env_prefix='APP_',
case_sensitive=False
)
# Environment variables
database_url: str
redis_url: str = 'redis://localhost:6379'
secret_key: SecretStr
debug: bool = False
# Nested settings
class SMTPSettings(BaseModel):
host: str
port: int = 587
username: str
password: SecretStr
smtp: SMTPSettings
# Reads from environment variables:
# APP_DATABASE_URL, APP_REDIS_URL, APP_SECRET_KEY, APP_DEBUG
# APP_SMTP__HOST, APP_SMTP__PORT, etc.
settings = AppSettings()
from functools import lru_cache
class Settings(BaseSettings):
environment: Literal['dev', 'staging', 'prod'] = 'dev'
database_url: str
api_key: SecretStr
model_config = SettingsConfigDict(
env_file='.env',
extra='ignore'
)
@property
def is_production(self) -> bool:
return self.environment == 'prod'
@lru_cache
def get_settings() -> Settings:
return Settings()
# Usage in FastAPI
from fastapi import Depends
@app.get('/config')
def get_config(settings: Settings = Depends(get_settings)):
return {'env': settings.environment}
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserCreate(BaseModel):
username: str = Field(min_length=3, max_length=50)
email: EmailStr
password: str = Field(min_length=8)
class UserResponse(BaseModel):
id: int
username: str
email: EmailStr
model_config = ConfigDict(from_attributes=True)
@app.post('/users', response_model=UserResponse)
def create_user(user: UserCreate):
# FastAPI auto-validates request body
# Returns only fields in UserResponse (password excluded)
return UserResponse(
id=1,
username=user.username,
email=user.email
)
from pydantic import BaseModel, Field
from fastapi import Query
class PaginationParams(BaseModel):
skip: int = Field(0, ge=0)
limit: int = Field(10, ge=1, le=100)
class SearchParams(BaseModel):
q: str = Field(..., min_length=1)
category: str | None = None
sort_by: Literal['date', 'relevance'] = 'relevance'
@app.get('/search')
def search(params: SearchParams = Query()):
return {'query': params.q, 'sort': params.sort_by}
class DetailedUser(BaseModel):
id: int
username: str
email: EmailStr
created_at: datetime
last_login: datetime | None
@app.get('/users/{user_id}', response_model=DetailedUser)
def get_user(user_id: int, include_dates: bool = False):
user = DetailedUser(
id=user_id,
username='alice',
email='alice@example.com',
created_at=datetime.now(),
last_login=None
)
if not include_dates:
return user.model_dump(exclude={'created_at', 'last_login'})
return user
from sqlalchemy import Column, Integer, String, DateTime
from sqlalchemy.orm import DeclarativeBase
from pydantic import BaseModel, ConfigDict
class Base(DeclarativeBase):
pass
# SQLAlchemy ORM model
class UserDB(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
username = Column(String(50), unique=True)
email = Column(String(100))
created_at = Column(DateTime, default=datetime.utcnow)
# Pydantic model for validation
class UserSchema(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
username: str
email: EmailStr
created_at: datetime
# Usage
from sqlalchemy.orm import Session
def get_user(db: Session, user_id: int) -> UserSchema:
user = db.query(UserDB).filter(UserDB.id == user_id).first()
return UserSchema.model_validate(user) # ORM → Pydantic
from pydantic import BaseModel
class UserBase(BaseModel):
username: str
email: EmailStr
class UserCreate(UserBase):
password: str
class UserUpdate(BaseModel):
username: str | None = None
email: EmailStr | None = None
password: str | None = None
class UserInDB(UserBase):
model_config = ConfigDict(from_attributes=True)
id: int
created_at: datetime
password_hash: str
# CRUD operations
def create_user(db: Session, user: UserCreate) -> UserInDB:
db_user = UserDB(
username=user.username,
email=user.email,
password_hash=hash_password(user.password)
)
db.add(db_user)
db.commit()
db.refresh(db_user)
return UserInDB.model_validate(db_user)
from django.db import models
from pydantic import BaseModel, field_validator
# Django model
class Article(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
published = models.BooleanField(default=False)
# Pydantic schema
class ArticleSchema(BaseModel):
model_config = ConfigDict(from_attributes=True)
title: str = Field(max_length=200)
content: str
published: bool = False
@field_validator('content')
@classmethod
def validate_content(cls, v: str) -> str:
if len(v) < 100:
raise ValueError('Content too short')
return v
# Usage in Django views
from django.http import JsonResponse
from django.views.decorators.http import require_http_methods
@require_http_methods(['POST'])
def create_article(request):
try:
data = ArticleSchema.model_validate_json(request.body)
article = Article.objects.create(**data.model_dump())
return JsonResponse({'id': article.id})
except ValidationError as e:
return JsonResponse({'errors': e.errors()}, status=400)
from pydantic import computed_field
class Rectangle(BaseModel):
width: float
height: float
@computed_field
@property
def area(self) -> float:
return self.width * self.height
@computed_field
@property
def perimeter(self) -> float:
return 2 * (self.width + self.height)
rect = Rectangle(width=10, height=5)
assert rect.area == 50
assert rect.perimeter == 30
# Computed fields in serialization
data = rect.model_dump()
# {'width': 10.0, 'height': 5.0, 'area': 50.0, 'perimeter': 30.0}
from pydantic import BaseModel, field_validator, ValidationError
from pydantic_core import PydanticCustomError
class StrictUser(BaseModel):
username: str
age: int
@field_validator('username')
@classmethod
def validate_username(cls, v: str) -> str:
if len(v) < 3:
raise PydanticCustomError(
'username_too_short',
'Username must be at least 3 characters',
{'min_length': 3, 'actual_length': len(v)}
)
return v
@field_validator('age')
@classmethod
def validate_age(cls, v: int) -> int:
if v < 18:
raise PydanticCustomError(
'underage',
'User must be at least 18 years old',
{'age': v, 'minimum_age': 18}
)
return v
# Custom error handling
try:
StrictUser(username='ab', age=16)
except ValidationError as e:
for error in e.errors():
print(f"{error['type']}: {error['msg']}")
print(f"Context: {error.get('ctx')}")
# Pydantic v2 uses pydantic-core (Rust) for:
# - 5-50x faster validation
# - Lower memory usage
# - Better error messages
# - Improved JSON parsing
import timeit
from pydantic import BaseModel
class Data(BaseModel):
values: list[int]
names: list[str]
metadata: dict[str, Any]
# Benchmark
data_dict = {
'values': list(range(1000)),
'names': ['item'] * 1000,
'metadata': {'key': 'value'}
}
def validate():
Data.model_validate(data_dict)
time_taken = timeit.timeit(validate, number=10000)
print(f"10000 validations: {time_taken:.2f}s")
from pydantic import BaseModel, ConfigDict
class OptimizedModel(BaseModel):
model_config = ConfigDict(
# Validate assignment only when needed
validate_assignment=False,
# Disable validation for internal use
validate_default=False,
# Use slots for memory efficiency
# (Not available in Pydantic v2 BaseModel directly)
)
data: list[int]
# Reuse validators
from functools import lru_cache
@lru_cache(maxsize=128)
def get_validator(model_class):
return model_class.model_validate
# Bulk validation
def validate_bulk(items: list[dict]) -> list[Data]:
validator = get_validator(Data)
return [validator(item) for item in items]
from pydantic import BaseModel, Field
class Product(BaseModel):
"""Product model for catalog"""
id: int = Field(description="Unique product identifier")
name: str = Field(description="Product name", examples=["Widget"])
price: float = Field(gt=0, description="Price in USD")
tags: list[str] = Field(default=[], description="Product tags")
# Generate JSON Schema
schema = Product.model_json_schema()
print(json.dumps(schema, indent=2))
# {
# "title": "Product",
# "description": "Product model for catalog",
# "type": "object",
# "properties": {
# "id": {"type": "integer", "description": "Unique product identifier"},
# "name": {"type": "string", "description": "Product name"},
# ...
# },
# "required": ["id", "name", "price"]
# }
# OpenAPI compatible
from fastapi import FastAPI
app = FastAPI()
@app.post('/products')
def create_product(product: Product):
return product
# FastAPI auto-generates OpenAPI schema from Pydantic models
from pydantic.dataclasses import dataclass
from pydantic import Field
@dataclass
class User:
id: int
name: str = Field(min_length=1)
email: str = Field(pattern=r'.+@.+\..+')
# Works like Pydantic BaseModel with validation
user = User(id=1, name='Alice', email='alice@example.com')
# Validation on construction
try:
User(id=2, name='', email='invalid')
except ValidationError as e:
print(e.errors())
# Convert to Pydantic BaseModel
from pydantic import BaseModel
class UserModel(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
name: str
email: str
user_model = UserModel.model_validate(user)
import pytest
from pydantic import ValidationError
def test_user_validation():
# Valid data
user = User(id=1, name='Alice', email='alice@example.com')
assert user.name == 'Alice'
# Invalid data
with pytest.raises(ValidationError) as exc_info:
User(id='invalid', name='Bob', email='bob@example.com')
errors = exc_info.value.errors()
assert errors[0]['type'] == 'int_parsing'
def test_user_serialization():
user = User(id=1, name='Alice', email='alice@example.com')
data = user.model_dump()
assert data == {
'id': 1,
'name': 'Alice',
'email': 'alice@example.com'
}
def test_nested_validation():
company = Company(
name='ACME',
address={'street': '123 Main', 'city': 'NYC', 'country': 'USA'}
)
assert company.address.city == 'NYC'
@pytest.fixture
def sample_user_data():
return {
'id': 1,
'name': 'Alice',
'email': 'alice@example.com'
}
@pytest.fixture
def sample_user(sample_user_data):
return User(**sample_user_data)
def test_with_fixtures(sample_user):
assert sample_user.name == 'Alice'
def test_invalid_email(sample_user_data):
sample_user_data['email'] = 'invalid'
with pytest.raises(ValidationError):
User(**sample_user_data)
from hypothesis import given, strategies as st
@given(
id=st.integers(min_value=1),
name=st.text(min_size=1, max_size=100),
email=st.emails()
)
def test_user_always_valid(id, name, email):
user = User(id=id, name=name, email=email)
assert user.id == id
assert user.name == name
assert user.email == email
# v1
from pydantic import BaseModel
class OldModel(BaseModel):
class Config:
validate_assignment = True
arbitrary_types_allowed = True
# Validators
@validator('field')
def validate_field(cls, v):
return v
@root_validator
def validate_model(cls, values):
return values
# Serialization
data = model.dict()
json_str = model.json()
# Parsing
model = OldModel.parse_obj(data)
model = OldModel.parse_raw(json_str)
# v2
from pydantic import BaseModel, ConfigDict, field_validator, model_validator
class NewModel(BaseModel):
model_config = ConfigDict(
validate_assignment=True,
arbitrary_types_allowed=True
)
# Field validators
@field_validator('field')
@classmethod
def validate_field(cls, v):
return v
# Model validators
@model_validator(mode='after')
def validate_model(self):
return self
# Serialization
data = model.model_dump()
json_str = model.model_dump_json()
# Parsing
model = NewModel.model_validate(data)
model = NewModel.model_validate_json(json_str)
class Config with model_config = ConfigDict().dict() → .model_dump().json() → .model_dump_json().parse_obj() → .model_validate().parse_raw() → .model_validate_json()@validator → @field_validator with @classmethod@root_validator → @model_validator(mode='after')json_encoders → use @field_serializer__get_pydantic_core_schema__# Separate schemas by use case
class UserBase(BaseModel):
"""Shared fields"""
username: str
email: EmailStr
class UserCreate(UserBase):
"""API request for creating user"""
password: str
class UserUpdate(BaseModel):
"""API request for updating user (all optional)"""
username: str | None = None
email: EmailStr | None = None
password: str | None = None
class UserInDB(UserBase):
"""Database representation"""
model_config = ConfigDict(from_attributes=True)
id: int
password_hash: str
created_at: datetime
class UserResponse(UserBase):
"""API response (excludes sensitive data)"""
id: int
created_at: datetime
# Use Field for constraints, not validators
class Good(BaseModel):
age: int = Field(ge=0, le=150)
email: EmailStr
class Bad(BaseModel):
age: int
email: str
@field_validator('age')
@classmethod
def validate_age(cls, v):
if v < 0 or v > 150:
raise ValueError('invalid age')
return v
# Prefer composition over inheritance
class TimestampMixin(BaseModel):
created_at: datetime = Field(default_factory=datetime.utcnow)
updated_at: datetime = Field(default_factory=datetime.utcnow)
class User(TimestampMixin):
username: str
email: EmailStr
from pydantic import ValidationError
def safe_validate(data: dict) -> User | None:
try:
return User.model_validate(data)
except ValidationError as e:
# Log validation errors
logger.error(f"Validation failed: {e.errors()}")
return None
def validate_with_details(data: dict):
try:
return User.model_validate(data)
except ValidationError as e:
# Return user-friendly errors
return {
'success': False,
'errors': [
{
'field': '.'.join(str(loc) for loc in err['loc']),
'message': err['msg'],
'type': err['type']
}
for err in e.errors()
]
}
from typing import Generic, TypeVar
T = TypeVar('T')
class APIResponse(BaseModel, Generic[T]):
success: bool
data: T | None = None
error: str | None = None
metadata: dict[str, Any] = {}
# Usage
user_response = APIResponse[User](
success=True,
data=User(id=1, name='Alice', email='alice@example.com')
)
error_response = APIResponse[User](
success=False,
error='User not found'
)
class PaginatedResponse(BaseModel, Generic[T]):
items: list[T]
total: int
page: int
page_size: int
@computed_field
@property
def total_pages(self) -> int:
return (self.total + self.page_size - 1) // self.page_size
users = PaginatedResponse[User](
items=[...],
total=100,
page=1,
page_size=10
)
assert users.total_pages == 10
class AuditMixin(BaseModel):
created_at: datetime = Field(default_factory=datetime.utcnow)
updated_at: datetime = Field(default_factory=datetime.utcnow)
created_by: int | None = None
updated_by: int | None = None
class Document(AuditMixin):
title: str
content: str
@model_validator(mode='before')
@classmethod
def update_timestamp(cls, data: dict) -> dict:
if isinstance(data, dict):
data['updated_at'] = datetime.utcnow()
return data
When using Pydantic, consider these complementary skills:
# FastAPI with Pydantic (basic pattern)
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserCreate(BaseModel):
username: str
email: EmailStr
password: str
class UserResponse(BaseModel):
id: int
username: str
email: EmailStr
model_config = ConfigDict(from_attributes=True)
@app.post('/users', response_model=UserResponse)
def create_user(user: UserCreate):
# FastAPI auto-validates using Pydantic
# response_model filters out password
return UserResponse(id=1, username=user.username, email=user.email)
# SQLAlchemy 2.0 with Pydantic validation
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import DeclarativeBase
from pydantic import BaseModel, ConfigDict
class Base(DeclarativeBase):
pass
class UserDB(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
username = Column(String(50))
email = Column(String(100))
class UserSchema(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
username: str
email: str
# Convert ORM to Pydantic
user_orm = db.query(UserDB).first()
user_validated = UserSchema.model_validate(user_orm)
# Testing Pydantic models with pytest
import pytest
from pydantic import ValidationError
def test_user_validation():
user = User(id=1, name='Alice', email='alice@example.com')
assert user.name == 'Alice'
def test_validation_error():
with pytest.raises(ValidationError) as exc_info:
User(id='invalid', name='Bob', email='bob@example.com')
errors = exc_info.value.errors()
assert errors[0]['type'] == 'int_parsing'
@pytest.fixture
def sample_user():
return User(id=1, name='Alice', email='alice@example.com')
[Full integration patterns available in respective skills if deployed together]