Pydantic is the go-to library for data validation and settings management using Python type hints. It powers FastAPI's request and response handling and is widely used across the Python ecosystem. In this guide, I'll walk you through defining models, validating input, managing configuration, and integrating Pydantic with your APIs and applications.
Why Pydantic?
Pydantic brings several benefits to Python projects:
- Type-based validation: Validate data using standard type hints
- Clear errors: Detailed validation errors with context
- Serialization: Easy conversion to and from JSON/dict
- Settings: Type-safe configuration from environment variables
- IDE support: Great autocomplete and type checking
Installation and Basic Models
Install Pydantic and define your first models:
pip install pydantic
from pydantic import BaseModel
from typing import Optional
class User(BaseModel):
id: int
name: str
email: str
age: Optional[int] = None
is_active: bool = True
# Validation happens automatically
user = User(id=1, name="Alice", email="alice@example.com")
print(user.model_dump()) # Serialize to dict
print(user.model_dump_json()) # Serialize to JSON
Validation and Custom Validators
Use built-in types and validators to enforce rules:
from pydantic import BaseModel, field_validator
from typing import Optional
class Product(BaseModel):
name: str
price: float
sku: str
@field_validator('price')
@classmethod
def price_must_be_positive(cls, v: float) -> float:
if v <= 0:
raise ValueError('price must be positive')
return v
@field_validator('sku')
@classmethod
def sku_uppercase(cls, v: str) -> str:
return v.upper()
# Valid data
product = Product(name="Widget", price=9.99, sku="abc-123")
# Invalid data raises ValidationError with clear messages
Settings Management
Pydantic Settings lets you load configuration from the environment with validation:
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
app_name: str = "My App"
debug: bool = False
database_url: str
api_key: str | None = None
model_config = {
"env_file": ".env",
"env_file_encoding": "utf-8",
"extra": "ignore"
}
settings = Settings() # Loads from environment and .env
print(settings.database_url)
Working with FastAPI
Pydantic and FastAPI work together seamlessly for request and response models:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class ItemCreate(BaseModel):
name: str
description: str | None = None
price: float
class ItemResponse(BaseModel):
id: int
name: str
description: str | None
price: float
@app.post("/items/", response_model=ItemResponse)
async def create_item(item: ItemCreate):
# item is already validated by Pydantic
return ItemResponse(id=1, **item.model_dump())
Advanced Features
Pydantic supports nested models, discriminators, and more:
- Nested models: Compose complex structures from smaller models
- model_validator: Validate the whole model after fields are set
- Computed fields: Add derived attributes with @computed_field
- JSON Schema: Generate OpenAPI/JSON Schema for documentation
Best Practices
Tips for using Pydantic effectively:
- Use separate models for input, internal state, and output when they differ
- Keep validators focused and reuse them with custom types
- Use Settings for all configuration; avoid ad-hoc env reads
- Rely on model_dump() and model_dump_json() for serialization
Conclusion
Pydantic makes data validation and configuration in Python straightforward and type-safe. Whether you're building APIs with FastAPI or any application that needs validated input and settings, Pydantic is a powerful choice.
Start with simple BaseModel classes and add validators and Settings as needed. Your future self will thank you for clear error messages and fewer runtime bugs.