Pydantic - Data Validation using Python Type
Hints
What is Pydantic?
Pydantic is a data validation and settings management library using Python type hints. It enforces type
hints at runtime and provides helpful error messages when data is invalid. Pydantic is widely used in
FastAPI and many other Python applications.
Key Features
Type Safety: Validates data using Python type hints
Fast: Core validation logic is written in Rust (Pydantic v2)
JSON Schema: Automatic JSON schema generation
IDE Support: Great editor support with auto-completion
Customizable: Extensive customization options for validation
Error Handling: Clear, detailed error messages
Serialization: Built-in serialization to dict/JSON
Installation
# Pydantic v2 (recommended)
pip install pydantic
# With email validation
pip install pydantic[email]
# Full installation
pip install pydantic[all]
Basic Usage
Simple Model
Page 1 of 9
from pydantic import BaseModel
from typing import Optional
from datetime import datetime
class User(BaseModel):
id: int
name: str
email: str
age: Optional[int] = None
created_at: datetime = datetime.now()
# Valid data
user = User(id=1, name="John Doe", email="[email protected]")
print(user)
# User(id=1, name='John Doe', email='[email protected]', age=None, created
# Invalid data raises ValidationError
try:
invalid_user = User(id="not_int", name="John") # Missing email, inva
except ValidationError as e:
print(e)
Field Validation
Built-in Validators
from pydantic import BaseModel, Field, EmailStr, validator
from typing import List
class Product(BaseModel):
name: str = Field(..., min_length=1, max_length=100)
price: float = Field(..., gt=0, description="Price must be greater th
tags: List[str] = Field(default_factory=list, max_items=5)
email: EmailStr # Requires pydantic[email]
rating: Optional[float] = Field(None, ge=0, le=5)
# Using Field for validation and metadata
class UserProfile(BaseModel):
username: str = Field(..., regex="^[a-zA-Z0-9_]+$", min_length=3)
age: int = Field(..., ge=0, le=120)
bio: Optional[str] = Field(None, max_length=500)
Page 2 of 9
Custom Validators
from pydantic import validator, root_validator
class UserAccount(BaseModel):
username: str
password: str
confirm_password: str
email: str
@validator('username')
def username_alphanumeric(cls, v):
assert v.isalnum(), 'Username must be alphanumeric'
return v
@validator('email')
def validate_email(cls, v):
if '@' not in v:
raise ValueError('Invalid email')
return v
@root_validator
def passwords_match(cls, values):
pw = values.get('password')
confirm_pw = values.get('confirm_password')
if pw is not None and confirm_pw is not None and pw != confirm_pw
raise ValueError('Passwords do not match')
return values
Advanced Features
Model Configuration
class ConfiguredModel(BaseModel):
name: str
value: int
class Config:
# Allow extra fields
Page 3 of 9
extra = "allow" # "ignore", "forbid"
# Case sensitivity
case_sensitive = False
# Validate assignments
validate_assignment = True
# Use enum values
use_enum_values = True
# JSON encoders
json_encoders = {
datetime: lambda v: v.isoformat()
}
Generic Models
from typing import TypeVar, Generic
DataT = TypeVar('DataT')
class Response(BaseModel, Generic[DataT]):
data: DataT
message: str
success: bool = True
# Usage
user_response = Response[User](
data=User(id=1, name="John", email="[email protected]"),
message="User created successfully"
)
Nested Models
class Address(BaseModel):
street: str
city: str
country: str
postal_code: str
Page 4 of 9
class Company(BaseModel):
name: str
address: Address
class Employee(BaseModel):
name: str
email: str
company: Company
addresses: List[Address] = []
# Usage
employee = Employee(
name="John Doe",
email="[email protected]",
company={
"name": "Tech Corp",
"address": {
"street": "123 Tech St",
"city": "San Francisco",
"country": "USA",
"postal_code": "94101"
}
}
)
Serialization & Deserialization
To Dict/JSON
user = User(id=1, name="John", email="[email protected]")
# To dictionary
user_dict = user.dict()
user_dict = user.dict(exclude={'created_at'})
user_dict = user.dict(include={'name', 'email'})
# To JSON
user_json = user.json()
user_json = user.json(exclude={'id'})
Page 5 of 9
From Dict/JSON
# From dictionary
user_data = {"id": 1, "name": "John", "email": "[email protected]"}
user = User(**user_data)
user = User.parse_obj(user_data)
# From JSON
json_str = '{"id": 1, "name": "John", "email": "[email protected]"}'
user = User.parse_raw(json_str)
Settings Management
from pydantic import BaseSettings
class Settings(BaseSettings):
database_url: str
secret_key: str
debug: bool = False
allowed_hosts: List[str] = ["localhost"]
class Config:
env_file = ".env"
env_file_encoding = "utf-8"
# Automatically loads from environment variables and .env file
settings = Settings()
FastAPI Integration
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, ValidationError
app = FastAPI()
class UserCreate(BaseModel):
name: str
email: str
age: Optional[int] = None
Page 6 of 9
class UserResponse(BaseModel):
id: int
name: str
email: str
age: Optional[int]
@app.post("/users/", response_model=UserResponse)
def create_user(user: UserCreate):
# Pydantic automatically validates the request body
# and converts the response to the specified model
new_user = UserResponse(
id=1,
name=user.name,
email=user.email,
age=user.age
)
return new_user
Error Handling
from pydantic import ValidationError
try:
user = User(id="invalid", name="", email="invalid-email")
except ValidationError as e:
print(e.json()) # JSON formatted errors
# Individual errors
for error in e.errors():
print(f"Field: {error['loc']}")
print(f"Error: {error['msg']}")
print(f"Type: {error['type']}")
Best Practices
1. Use Type Hints Consistently
Page 7 of 9
from typing import Optional, List, Dict, Union
from datetime import datetime
class WellTypedModel(BaseModel):
id: int
name: str
tags: List[str]
metadata: Dict[str, Union[str, int]]
created_at: Optional[datetime] = None
2. Separate Input/Output Models
class UserBase(BaseModel):
name: str
email: str
class UserCreate(UserBase):
password: str
class UserUpdate(BaseModel):
name: Optional[str] = None
email: Optional[str] = None
class UserInDB(UserBase):
id: int
hashed_password: str
created_at: datetime
class UserResponse(UserBase):
id: int
created_at: datetime
3. Use Aliases for External APIs
class ExternalAPIModel(BaseModel):
user_name: str = Field(alias="userName")
user_email: str = Field(alias="userEmail")
class Config:
allow_population_by_field_name = True # Accept both alias and fi
Page 8 of 9
Pydantic provides robust data validation, serialization, and type safety, making it an essential tool for
modern Python applications.
Page 9 of 9