Skip to main content
Components Data Validation

Pydantic

Core Stack

Data validation using Python type hints

Version
2.5.0
Last Updated
2024-01-12
Difficulty
Beginner
Reading Time
4 min

Pydantic

Pydantic is a data validation library that uses Python type hints to validate, serialize, and deserialize data. It’s the foundation for FastAPI’s automatic request/response validation.

Key Features

  • Type-Safe Validation: Leverage Python type hints for automatic validation
  • High Performance: Built on top of Rust for excellent performance
  • JSON Schema Generation: Automatic schema generation for APIs
  • IDE Support: Excellent autocomplete and type checking
  • Extensive Customization: Custom validators and serializers

Installation

1
pip install pydantic

Quick Start

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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()

# Create a user
user_data = {
    "id": 1,
    "name": "John Doe",
    "email": "[email protected]",
    "age": 30
}

user = User(**user_data)
print(user)
# User(id=1, name='John Doe', email='[email protected]', age=30, created_at=datetime.datetime(...))

# JSON serialization
print(user.model_dump_json())

Core Concepts

Basic Models

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
from pydantic import BaseModel, Field
from typing import List

class Address(BaseModel):
    street: str
    city: str
    country: str = "USA"

class Person(BaseModel):
    name: str = Field(..., min_length=1, max_length=100)
    age: int = Field(..., ge=0, le=150)
    addresses: List[Address] = []
    
person = Person(
    name="Alice",
    age=25,
    addresses=[
        {"street": "123 Main St", "city": "New York"},
        {"street": "456 Oak Ave", "city": "Boston"}
    ]
)

Custom Validators

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from pydantic import BaseModel, validator, root_validator
import re

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 email_valid(cls, v):
        pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
        if not re.match(pattern, v):
            raise ValueError('Invalid email format')
        return v
    
    @root_validator
    def passwords_match(cls, values):
        pw1, pw2 = values.get('password'), values.get('confirm_password')
        if pw1 is not None and pw2 is not None and pw1 != pw2:
            raise ValueError('Passwords do not match')
        return values

Settings Management

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
from pydantic import BaseSettings

class Settings(BaseSettings):
    app_name: str = "My App"
    debug: bool = False
    database_url: str
    secret_key: str
    
    class Config:
        env_file = ".env"

settings = Settings()

Use Cases

  • API Request/Response Validation: Perfect for FastAPI and other web frameworks
  • Configuration Management: Type-safe configuration from environment variables
  • Data Serialization/Deserialization: Convert between Python objects and JSON
  • Settings and Environment Variables: Manage application settings with validation

Best Practices

  1. Use Type Hints: Always specify types for better validation and IDE support
  2. Field Validation: Use Field() for additional constraints and metadata
  3. Custom Validators: Implement business logic validation with custom validators
  4. Nested Models: Break complex data into smaller, reusable models
  5. Error Handling: Handle ValidationError exceptions gracefully

Common Patterns

API Models

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
from pydantic import BaseModel
from typing import Optional
from datetime import datetime

class CreateUserRequest(BaseModel):
    name: str
    email: str
    age: Optional[int] = None

class UserResponse(BaseModel):
    id: int
    name: str
    email: str
    age: Optional[int]
    created_at: datetime
    
    class Config:
        from_attributes = True  # For SQLAlchemy models

Configuration with Environment Variables

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
from pydantic import BaseSettings, Field

class DatabaseSettings(BaseSettings):
    host: str = Field(..., env='DB_HOST')
    port: int = Field(5432, env='DB_PORT')
    username: str = Field(..., env='DB_USER')
    password: str = Field(..., env='DB_PASSWORD')
    database: str = Field(..., env='DB_NAME')
    
    @property
    def url(self) -> str:
        return f"postgresql://{self.username}:{self.password}@{self.host}:{self.port}/{self.database}"
    
    class Config:
        env_file = ".env"

Data Transformation

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
from pydantic import BaseModel, validator

class Product(BaseModel):
    name: str
    price: float
    category: str
    
    @validator('name')
    def name_must_be_title_case(cls, v):
        return v.title()
    
    @validator('price')
    def price_must_be_positive(cls, v):
        if v <= 0:
            raise ValueError('Price must be positive')
        return round(v, 2)
    
    @validator('category')
    def category_to_lowercase(cls, v):
        return v.lower()

Integration with FastAPI

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    name: str
    price: float
    is_offer: bool = False

@app.post("/items/")
async def create_item(item: Item):
    # Pydantic automatically validates the request body
    return {"item": item, "message": "Item created successfully"}

@app.get("/items/{item_id}")
async def read_item(item_id: int) -> Item:
    # Pydantic validates the response
    return Item(name="Example", price=10.99, is_offer=True)

Resources

Alternatives

Marshmallow

Object serialization/deserialization library

Key Strengths:
• Mature and stable
• Flexible serialization
Best For:
• API serialization
• Data transformation
Intermediate

Cerberus

Lightweight data validation library

Key Strengths:
• Simple and lightweight
• Flexible validation rules
Best For:
• Simple validation tasks
• Configuration validation
Beginner

Quick Decision Guide

Choose Pydantic for the recommended stack with proven patterns and comprehensive support.
Choose Marshmallow if you need api serialization or similar specialized requirements.
Choose Cerberus if you need simple validation tasks or similar specialized requirements.