Quick Reference · data validation & serialization · v2

pydantic · cheat sheet

One idea underneath everything: your type annotations compile to a core schema (in Rust) that does two jobs — validate untrusted input into a typed instance, and serialize that instance back to a dict or JSON. Validators hook the way in; serializers hook the way out. This is v2 — every method is model_*.

define fields & types validate serialize custom logic config & advanced gotcha / v1 most common

Introspected from Pydantic 2.13.4 / pydantic-core 2.46 (installed & run) & cross-checked with: docs.pydantic.dev (official) · github.com/pydantic/pydantic · the v1→v2 Migration Guide · PyPI

One schema, two directions — validate in, serialize out
RAW INPUT {"id": "1", "name": …} dict '{"id": 1, …}' JSON orm_obj (attributes) validate class User(BaseModel) id: int · name: str age: int = 0 ↳ compiled core schema · Rust @field_validator User(id=1, …) typed & guaranteed serialize OUTPUT .model_dump() → dict .model_dump_json() → str @field_serializer MOVING FROM v1? — the method renames that trip everyone up .dict().model_dump() .json().model_dump_json() .parse_obj().model_validate() .schema().model_json_schema() class Configmodel_config=ConfigDict @validator@field_validator @root_validator@model_validator .copy().model_copy() annotate the types · validate in · serialize out — the whole library is that round trip, plus hooks on each side
quickstart · define · validate · serialize
from pydantic import BaseModel, Field, field_validator
from datetime import datetime

class User(BaseModel):
    id: int
    name: str = "anon"
    age: int = Field(gt=0, le=120)          # constraint
    joined: datetime | None = None

    @field_validator("name")
    @classmethod
    def titlecase(cls, v): return v.title()

u = User.model_validate({"id": "1", "name": "ada", "age": 36})
# "1" coerced → 1 ; name → "Ada"
u.model_dump()          # {'id': 1, 'name': 'Ada', 'age': 36, 'joined': None}
u.model_dump_json()     # '{"id":1,"name":"Ada",...}'
01Setup & define a modelBaseModel
02Validate inputraw → typed instance
03Serialize / dumpinstance → dict / JSON
04Field()defaults & constraints
05The Annotated patternreusable constraints
06Common field typesstdlib annotations
07Pydantic typesbatteries included
08field_validatorper-field logic
09model_validatorwhole-model / cross-field
10Annotated validatorsreusable, on the type
11Custom serializersshape the output
12computed_fieldderived output
13ConfigDictmodel_config = …
14Aliasesexternal ↔ field names
15TypeAdaptervalidate without a model
16RootModel & dynamicnon-dict roots
17Errors & strictnesscoercion control
18Settings & functionspydantic-settings
19v1 → v2 migrationwhat changed

Four ideas that make the rest obvious

Everything above is one of these four patterns in disguise. Learn the shapes, not the method list.

one schema, two jobs

Annotations compile to a core schema that both validates input and serializes output. Same schema, opposite directions.

annotations core schema · Rust raw dict / json dict / json out validate serialize

validator modes: before · after · wrap

Where your function sits relative to core coercion. before sees raw input; after sees the coerced value; wrap straddles it.

raw before core coerce after (default) — post-coercion value wrap calls core itself — full control

field_validator vs model_validator

Scope decides which to use: one field's value, or the whole model where you can compare fields.

@field_validator email ← this one age name classmethod · one value return v @model_validator email · age · name all fields at once if a > b: raise self · cross-field return self

model_dump flags → different output

One instance, many shapes. The flags decide type, which keys, and which names appear.

instance model_dump() joined: datetime(…) python objects mode='json' joined: "2026-…" json-safe by_alias=True userId: 1 alias keys exclude_unset=True only fields the caller actually set → PATCH

Worth memorizing

everything is model_*.dict/.json/.parse_obj are v1-deprecated → model_dump/_json/validate
class Config is goneuse model_config = ConfigDict(...)
field_validator = classmethodbut model_validator(mode='after') is an instance method — return self
lax by default"5"→5; use strict=True or StrictInt to forbid coercion
mutable defaultsField(default_factory=list), never = []
Optional isn't optionalx: int | None still requires the key — add = None
mode='json' for JSON dictplain model_dump() keeps datetime/UUID as objects
exclude_unsetdumps only what was set — the PATCH-request pattern
prefer AnnotatedAnnotated[int, Field(gt=0)] beats x: int = Field(...)
TypeAdaptervalidate list[int] & friends with no model; build once, reuse
settings moved outpip install pydantic-settings — separate package in v2
before vs afterbefore sees raw input; after sees the coerced value