pydantic β Data Validation#
What it is#
Pydantic validates data against Python type annotations at runtime. You define a BaseModel with type-annotated fields; Pydantic enforces types, coerces compatible values, and raises detailed errors for invalid input. It is the foundation of FastAPIβs request/response handling and is widely used for configuration, API clients, and data parsing.
[!NOTE] This page covers Pydantic v2 (released 2023, current as of 2026). The API changed significantly from v1. Check your installed version:
python -c "import pydantic; print(pydantic.__version__)".
Install#
pip install pydantic
# Optional: EmailStr and other validators
pip install "pydantic[email]"
Quick example#
from pydantic import BaseModel, ValidationError
class User(BaseModel):
name: str
age: int
active: bool = True
u = User(name="Alice", age=30)
print(u.model_dump())
try:
User(name="Bob", age="not-a-number")
except ValidationError as e:
print(f"{e.error_count()} validation error(s)")
print(e.errors()[0]["msg"])
Output:
{'name': 'Alice', 'age': 30, 'active': True}
1 validation error(s)
Input should be a valid integer, unable to parse string as an integer
When / why to use it#
- Parsing and validating API request bodies or responses.
- Typed configuration objects loaded from env vars or YAML.
- Any time you want Python type hints to be enforced at runtime, not just as documentation.
Common pitfalls#
[!WARNING] v1 β v2 migration β Pydantic v2 renamed many methods:
dict()βmodel_dump(),json()βmodel_dump_json(),parse_obj()βmodel_validate(). Using v1 syntax on v2 raisesAttributeError.
[!WARNING] Mutable defaults β use
Field(default_factory=list)for mutable defaults like lists and dicts, not bare= []. Bare mutable defaults are shared across all instances (same as Python dataclass gotcha).
[!TIP] Use
model_config = ConfigDict(strict=True)to disable Pydanticβs coercion. By default"30"is silently coerced to30for anintfield, which can hide bugs.
Richer example β nested models and validators#
from pydantic import BaseModel, field_validator, Field
from typing import Optional
class Address(BaseModel):
street: str
city: str
zip_code: str
class User(BaseModel):
name: str
age: int = Field(ge=0, le=130, description="Must be 0β130")
email: str
tags: list[str] = Field(default_factory=list)
address: Optional[Address] = None
@field_validator("email")
@classmethod
def email_must_contain_at(cls, v: str) -> str:
if "@" not in v:
raise ValueError("not a valid email address")
return v.lower()
u = User(
name="Alice",
age=30,
email="Alice@Example.COM",
tags=["admin", "user"],
address={"street": "123 Main St", "city": "Anytown", "zip_code": "12345"},
)
print(u.model_dump_json(indent=2))
Output:
{
"name": "Alice",
"age": 30,
"email": "alice@example.com",
"tags": [
"admin",
"user"
],
"address": {
"street": "123 Main St",
"city": "Anytown",
"zip_code": "12345"
}
}
JSON and dict conversion#
# From dict
user = User.model_validate({"name": "Bob", "age": 25, "email": "b@b.com"})
# From JSON string
user = User.model_validate_json('{"name":"Bob","age":25,"email":"b@b.com"}')
# To dict
d = user.model_dump()
d = user.model_dump(exclude={"email"}) # exclude fields
d = user.model_dump(include={"name", "age"}) # include only
# To JSON string
j = user.model_dump_json()
j = user.model_dump_json(indent=2)
Settings from environment variables#
from pydantic_settings import BaseSettings # pip install pydantic-settings
class Settings(BaseSettings):
database_url: str
api_key: str
debug: bool = False
class Config:
env_file = ".env"
settings = Settings() # reads DATABASE_URL, API_KEY, DEBUG from env / .env
print(settings.debug)
Output:
False