Python Pydantic Cheat Sheet
Defining BaseModel schemas, validators, field constraints, and serialization with Pydantic v2 for runtime data validation and settings.
Basic Models
Define fields with type hints; Pydantic validates and coerces on construction.
from pydantic import BaseModel, Fieldfrom typing import Optionalfrom datetime import datetimeclass User(BaseModel): id: int name: str email: str signup_date: datetime = Field(default_factory=datetime.utcnow) bio: Optional[str] = Noneuser = User(id=1, name="Alice", email="[email protected]")print(user.model_dump()) # dictprint(user.model_dump_json()) # JSON string
Field & Model Validators (v2)
field_validator and model_validator replace v1's @validator/@root_validator.
from pydantic import BaseModel, field_validator, model_validatorclass SignupForm(BaseModel): password: str confirm_password: str @field_validator("password") @classmethod def password_min_length(cls, v: str) -> str: if len(v) < 8: raise ValueError("password must be at least 8 characters") return v @model_validator(mode="after") def passwords_match(self): if self.password != self.confirm_password: raise ValueError("passwords do not match") return self
Field Constraints & Config
Constrain values inline and configure model-wide behavior.
from pydantic import BaseModel, Field, ConfigDictclass Product(BaseModel): model_config = ConfigDict(str_strip_whitespace=True, extra="forbid") name: str = Field(min_length=1, max_length=100) price: float = Field(gt=0, description="Price in USD") quantity: int = Field(ge=0, default=0) tags: list[str] = Field(default_factory=list, max_length=10)# extra="forbid" rejects unknown fields at construction time
Settings Management
pydantic-settings reads env vars / .env files into a typed model.
from pydantic_settings import BaseSettings, SettingsConfigDictclass Settings(BaseSettings): model_config = SettingsConfigDict(env_file=".env", env_prefix="APP_") debug: bool = False database_url: str max_connections: int = 10settings = Settings() # reads APP_DEBUG, APP_DATABASE_URL, etc.
Error Handling & Types
Common patterns for catching and inspecting validation errors.
- ValidationError- raised on construction; e.errors() gives structured error list
- model_validate(obj)- validate a dict/object into a model instance
- model_validate_json(s)- validate directly from a JSON string
- TypeAdapter- validate non-BaseModel types like list[int] or dict[str, User]
- StrictStr / StrictInt- disable type coercion for a field (no '5' -> 5)
- Annotated[str, Field(...)]- preferred way to attach constraints in v2
Discriminated Unions
Field(discriminator=...) makes Pydantic pick the correct union member by a tag field instead of trying each type in order, giving clearer errors and better performance.
from typing import Literal, Unionfrom pydantic import BaseModel, Fieldclass Cat(BaseModel): pet_type: Literal["cat"] meows: intclass Dog(BaseModel): pet_type: Literal["dog"] barks: floatclass Owner(BaseModel): pet: Union[Cat, Dog] = Field(discriminator="pet_type")owner = Owner.model_validate({"pet": {"pet_type": "dog", "barks": 3.5}})print(type(owner.pet)) # <class '__main__.Dog'># Without a discriminator, Pydantic tries each member in order ("smart# union"), which is slower and can pick the wrong type for ambiguous data.
Custom Serialization
field_serializer and model_serializer control exactly how values are turned into dicts/JSON, independent of validation.
from pydantic import BaseModel, field_serializer, model_serializerfrom decimal import Decimalclass Invoice(BaseModel): amount: Decimal currency: str @field_serializer("amount") def serialize_amount(self, v: Decimal) -> str: return f"{v:.2f}" @model_serializer(mode="wrap") def add_display(self, handler): data = handler(self) data["display"] = f"{data['amount']} {self.currency}" return datainv = Invoice(amount=Decimal("9.5"), currency="USD")print(inv.model_dump())# {'amount': '9.50', 'currency': 'USD', 'display': '9.50 USD'}
validate_call Decorator
Apply Pydantic validation directly to plain function arguments and return values without wrapping them in a model.
from pydantic import validate_call, Fieldfrom typing import Annotated@validate_calldef send_email( to: str, subject: str, retries: Annotated[int, Field(ge=0, le=5)] = 3,) -> bool: ... return Truesend_email(to="[email protected]", subject="Hi", retries=2) # OK, coerced/validatedsend_email(to="[email protected]", subject="Hi", retries=10) # raises ValidationError# validate_call(validate_return=True) also validates the return value
Generic Models
BaseModel subclasses can be parameterized with TypeVar to build reusable, type-safe wrappers like paginated response envelopes.
from typing import Generic, TypeVarfrom pydantic import BaseModelT = TypeVar("T")class Page(BaseModel, Generic[T]): items: list[T] total: int page: int = 1class Item(BaseModel): id: int name: strresult = Page[Item].model_validate({ "items": [{"id": 1, "name": "widget"}], "total": 1,})print(result.items[0].name) # "widget", fully typed
Advanced Model Config & Fields
Config knobs and helpers beyond basic field constraints, useful once a model interfaces with external APIs.
- alias_generator- ConfigDict option to auto-derive aliases (e.g. to_camel) for every field, for JSON APIs with camelCase
- populate_by_name=True- allows constructing a model by field name even when an alias is set
- computed_field- decorator to include a derived @property in model_dump()/JSON output
- RootModel- wraps a non-dict top-level type (e.g. a bare list or scalar) as a validated model
- model_config = ConfigDict(frozen=True)- makes instances immutable and hashable
- json_schema_extra- inject custom OpenAPI/JSON Schema metadata (examples, format) into a field or model
- TypeAdapter(...).json_schema()- generate a JSON Schema for types that aren't BaseModel subclasses
Use model_config = ConfigDict(extra='forbid') on any model that parses external input (API request bodies, config files) — silently ignoring unexpected fields hides typos and API drift that you'd otherwise catch immediately.