
Mastering Pydantic for Data Validation and Settings Management in Python
Core Concepts of Pydantic
At the heart of Pydantic is the BaseModel class. By subclassing BaseModel, you can define a data structure with type hints, and Pydantic will automatically validate any input data against those types.
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str
email: str
is_active: bool = True # default valueIn the example above, any attempt to instantiate the User class with invalid data will raise a ValidationError.
user = User(id="not-an-integer", name="Alice", email="[email protected]")
# Raises: ValueError: invalid literal for int() with base 10: 'not-an-integer'Data Parsing and Conversion
Pydantic also supports data parsing and conversion. It can convert input data into the expected types, making it easier to work with unstructured data like JSON or dictionaries.
data = {
"id": "123",
"name": "Bob",
"email": "[email protected]",
"is_active": "false"
}
user = User(**data)
print(user.id) # 123 (converted from string)
print(user.is_active) # False (converted from string)This feature is particularly useful when dealing with APIs, where data types may not be consistent.
Custom Validation with @validator
Pydantic allows custom validation logic using @validator decorators. This is useful for enforcing business rules or complex constraints.
from pydantic import BaseModel, validator
class Product(BaseModel):
name: str
price: float
tax_rate: float = 0.1 # default tax rate
@validator("price")
def price_must_be_positive(cls, v):
if v <= 0:
raise ValueError("Price must be positive.")
return vIn this example, the price field is validated to ensure it is a positive number.
Field Configuration
Pydantic provides a way to configure fields using Field and Config classes. These are used to set default values, aliases, and field metadata.
from pydantic import BaseModel, Field
class OrderItem(BaseModel):
product_id: int
quantity: int = Field(default=1, ge=1, le=10)
unit_price: float = Field(..., description="Price per unit", example=10.99)
class Config:
frozen = True # prevents modification after creationThis configuration ensures that quantity is an integer between 1 and 10, and that the OrderItem object is immutable after creation.
Settings Management with BaseSettings
Pydantic supports environment-based configuration via the BaseSettings class, which is ideal for managing application settings.
from pydantic import BaseSettings, Field
class AppConfig(BaseSettings):
app_name: str
debug: bool = False
api_key: str = Field(..., env="API_KEY")
class Config:
env_file = ".env"
env_file_encoding = "utf-8"In this example, AppConfig reads environment variables from a .env file. If API_KEY is not provided, Pydantic raises a ValueError.
Data Models and API Development
Pydantic is especially valuable in API development. It can be used to define request and response models, ensuring that data is validated before and after processing.
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class ItemCreateRequest(BaseModel):
name: str
description: str = None
price: float
tax: float = None
@app.post("/items/")
def create_item(item: ItemCreateRequest):
return item.dict()In this FastAPI example, ItemCreateRequest is used to validate the input of the /items/ endpoint.
Advanced Features
Pydantic supports advanced features such as root models, recursive models, and arbitrary types.
Root Model
A RootModel is used when the data structure is not a dictionary or list.
from pydantic import RootModel
class UserList(RootModel):
root: list[str]
users = UserList(root=["Alice", "Bob"])
print(users.root) # ['Alice', 'Bob']Recursive Models
Pydantic allows models to reference themselves, which is useful for hierarchical data.
class Category(BaseModel):
id: int
name: str
subcategories: list["Category"] = []
category1 = Category(id=1, name="Books", subcategories=[])
category2 = Category(id=2, name="Fiction", subcategories=[category1])Arbitrary Type Support
Pydantic supports arbitrary types using the arbitrary_types_allowed option in the Config class.
from datetime import datetime
from pydantic import BaseModel
class Event(BaseModel):
name: str
start_time: datetime
class Config:
arbitrary_types_allowed = TrueThis allows Pydantic to handle complex types like datetime or custom classes.
Performance Considerations
Pydantic is optimized for performance and is generally faster than other validation libraries. However, for large-scale applications, consider using caching and batch validation where possible.
