Pydantic v2 (Latest: v2.12.5)
Write correct Pydantic v2 code. LLMs commonly generate deprecated v1 patterns — follow this guide strictly.
Critical Rules — Never Violate These
1. No class Config: — Use model_config = ConfigDict(...)
# WRONG
class MyModel(BaseModel):
class Config:
frozen = True
# CORRECT
from pydantic import BaseModel, ConfigDict
class MyModel(BaseModel):
model_config = ConfigDict(frozen=True)
2. No @validator — Use @field_validator
# WRONG
from pydantic import validator
@validator('name')
def check(cls, v):
return v
# CORRECT
from pydantic import field_validator
@field_validator('name')
@classmethod
def check(cls, v: str) -> str:
if not v.strip():
raise ValueError('empty')
return v.strip()
Key differences from v1:
@classmethodis requiredpre=Truebecomesmode='before'(default ismode='after')each_item=Trueis removed — useAnnotatedon inner type insteadalways=Trueis removed — usevalidate_default=Truein Field() or configvaluesparameter is gone — useinfo: ValidationInfoandinfo.data- Must explicitly return the value (forgetting return silently sets field to None)
3. No @root_validator — Use @model_validator
# WRONG
from pydantic import root_validator
@root_validator
def check(cls, values):
return values
# CORRECT — mode='after' (instance method, NOT classmethod)
from pydantic import model_validator
from typing import Self
@model_validator(mode='after')
def check(self) -> Self:
if self.password != self.confirm:
raise ValueError('mismatch')
return self
# CORRECT — mode='before' (classmethod, receives raw data)
@model_validator(mode='before')
@classmethod
def preprocess(cls, data: Any) -> Any:
if isinstance(data, dict):
# transform data
pass
return data
4. No .dict() / .json() — Use .model_dump() / .model_dump_json()
| v1 (REMOVED) | v2 (CORRECT) |
|---------------------------|-------------------------------|
| .dict() | .model_dump() |
| .json() | .model_dump_json() |
| .parse_obj(data) | .model_validate(data) |
| .parse_raw(json_str) | .model_validate_json(json) |
| .schema() | .model_json_schema() |
| .copy(update={...}) | .model_copy(update={...}) |
| .construct(**data) | .model_construct(**data) |
| .update_forward_refs() | .model_rebuild() |
| .__fields__ | .model_fields |
| .__fields_set__ | .model_fields_set |
5. constr / conint / confloat Are Legacy — Prefer Annotated
These helpers still work in Pydantic v2 without deprecation warnings, but the Annotated pattern is the recommended modern style. Prefer Annotated in new code; no need to urgently rewrite existing uses.
# LEGACY (works, but discouraged in new code)
from pydantic import constr, conint
name: constr(min_length=1, max_length=50)
age: conint(ge=0)
# RECOMMENDED
from typing import Annotated
from pydantic import Field
from pydantic.types import StringConstraints
name: Annotated[str, StringConstraints(min_length=1, max_length=50)]
age: Annotated[int, Field(ge=0)]
6. No GenericModel — Use BaseModel + Generic[T]
# WRONG
from pydantic.generics import GenericModel
# CORRECT
from typing import Generic, TypeVar
from pydantic import BaseModel
T = TypeVar('T')
class Response(BaseModel, Generic[T]):
data: T
count: int
7. No __root__ — Use RootModel
# WRONG
class Items(BaseModel):
__root__: list[str]
# CORRECT
from pydantic import RootModel
class Items(RootModel[list[str]]):
pass
8. Optional[X] Does NOT Imply default=None
# v2: This is REQUIRED (no default!)
name: str | None
# v2: This has a default of None
name: str | None = None
Config Renames (v1 -> v2)
| v1 (REMOVED) | v2 (CORRECT) |
|--------------------------------------|------------------------|
| orm_mode = True | from_attributes=True |
| allow_population_by_field_name | populate_by_name |
| validate_all | validate_default |
| allow_mutation = False | frozen=True |
| schema_extra | json_schema_extra |
| anystr_strip_whitespace | str_strip_whitespace |
| anystr_lower / anystr_upper | str_to_lower / str_to_upper |
| max_anystr_length / min_anystr_length | str_max_length / str_min_length |
Field() Parameter Renames
| v1 (REMOVED) | v2 (CORRECT) |
|----------------------|------------------------------------|
| regex=... | pattern=... |
| min_items=... | min_length=... |
| max_items=... | max_length=... |
| const=True | Use Literal[value] type |
| unique_items=True | Use set[T] or frozenset[T] |
| allow_mutation=False| frozen=True |
Quick Reference: Common Patterns
Model with Config
from pydantic import BaseModel, ConfigDict, Field
class User(BaseModel):
model_config = ConfigDict(
strict=True,
frozen=True,
extra='forbid',
from_attributes=True,
)
name: str = Field(min_length=1, max_length=100)
age: int = Field(ge=0, le=150)
email: str | None = None
Validators (Decorator vs Annotated)
from typing import Annotated
from pydantic import BaseModel, Field, field_validator, AfterValidator, BeforeValidator
# Annotated style — preferred for reusable types
def ensure_positive(v: int) -> int:
if v <= 0:
raise ValueError('must be positive')
return v
PositiveInt = Annotated[int, AfterValidator(ensure_positive)]
class Order(BaseModel):
quantity: PositiveInt
name: str
# Decorator style — for model-specific logic
@field_validator('name')
@classmethod
def strip_name(cls, v: str) -> str:
return v.strip()
Serialization
from pydantic import BaseModel, field_serializer, computed_field
class User(BaseModel):
first: str
last: str
joined: datetime
@computed_field
@property
def full_name(self) -> str: # return type REQUIRED
return f'{self.first} {self.last}'
@field_serializer('joined')
def ser_joined(self, v: datetime, _info) -> str:
return v.isoformat()
# Dump options
u.model_dump(exclude_none=True, by_alias=True, mode='json')
u.model_dump_json(indent=2)
Aliases (Three Types)
from pydantic import BaseModel, Field, AliasPath, AliasChoices, ConfigDict
class User(BaseModel):
model_config = ConfigDict(populate_by_name=True)
name: str = Field(alias='userName') # both input & output
email: str = Field(validation_alias='email_address') # input only
age: int = Field(serialization_alias='user_age') # output only
# Nested access
city: str = Field(validation_alias=AliasPath('address', 'city'))
# Multiple options
phone: str = Field(validation_alias=AliasChoices('phone', 'tel', 'mobile'))
TypeAdapter (Validate Without a Model)
from pydantic import TypeAdapter
adapter = TypeAdapter(list[int])
result = adapter.validate_python(['1', '2', '3']) # [1, 2, 3]
json_bytes = adapter.dump_json(result)
schema = adapter.json_schema()
Discriminated Unions
from typing import Literal, Annotated, Union
from pydantic import BaseModel, Field, Discriminator, Tag
class Cat(BaseModel):
pet_type: Literal['cat']
meows: int
class Dog(BaseModel):
pet_type: Literal['dog']
barks: float
class Home(BaseModel):
pet: Cat | Dog = Field(discriminator='pet_type')
More Common Patterns
Forward References & model_rebuild()
- Self-referencing models need
model_rebuild()after definition from __future__ import annotationsworks but requiresmodel_rebuild()
from __future__ import annotations
from pydantic import BaseModel
class Node(BaseModel):
value: int
children: list[Node] = []
Node.model_rebuild() # Required when using forward references
node = Node(value=1, children=[Node(value=2)])
model_construct() — Skip Validation
- For creating instances from trusted data without validation overhead
user = User.model_construct(name='John', age=30)
# No validation runs — use only with trusted data
# _fields_set parameter tracks which fields were explicitly provided
user = User.model_construct(_fields_set={'name'}, name='John', age=30)
Extra Fields Handling
- Three modes:
'ignore'(default),'forbid','allow'
from pydantic import BaseModel, ConfigDict
class Strict(BaseModel):
model_config = ConfigDict(extra='forbid') # Raises on unknown fields
name: str
class Flexible(BaseModel):
model_config = ConfigDict(extra='allow') # Stores unknown fields
name: str
f = Flexible(name='John', role='admin')
f.model_extra # {'role': 'admin'} — access via model_extra
f.model_dump() # {'name': 'John', 'role': 'admin'}
Key Imports Cheat Sheet
# Core
from pydantic import BaseModel, Field, ConfigDict, RootModel, PrivateAttr
# Validators
from pydantic import field_validator, model_validator, validate_call
from pydantic import AfterValidator, BeforeValidator, PlainValidator, WrapValidator
from pydantic import ValidationError, ValidationInfo
# Serialization
from pydantic import field_serializer, model_serializer, computed_field
from pydantic import PlainSerializer, WrapSerializer
# Aliases
from pydantic import AliasPath, AliasChoices, AliasGenerator
from pydantic.alias_generators import to_camel, to_snake, to_pascal
# Types & Constraints
from pydantic import TypeAdapter, create_model
from pydantic.types import StringConstraints, Strict
from pydantic import Discriminator, Tag
# Errors
from pydantic_core import PydanticCustomError
# Self type for model_validator(mode='after')
from typing import Self
Gotchas — Top LLM Mistakes
The most common errors Claude and other LLMs make with Pydantic. Full 28-item catalogue in references/v1-to-v2-migration.md § 9.
@validator→@field_validator— and you MUST add@classmethod(v2 does not infer it)class Config:→model_config = ConfigDict(...)— inner Config class is deprecated.dict()/.json()→.model_dump()/.model_dump_json()— all old method names removed@root_validator→@model_validator—mode='after'is an instance method (self), NOT a classmethod;mode='before'IS a classmethod- Forgetting
return vin validators — silently sets the field toNone Optional[X]no longer impliesdefault=None—str | Noneis REQUIRED; you must writestr | None = Nonefor a defaultpre=True→mode='before',values→info: ValidationInfo+info.dataorm_mode=True→from_attributes=True,schema_extra→json_schema_extraGenericModelremoved — useBaseModel, Generic[T]directlyBaseSettingsmoved —from pydantic_settings import BaseSettings(separate package)
Reference Files
Read these on-demand for deeper details:
references/v1-to-v2-migration.md— Start here when debugging wrong Pydantic output. Exhaustive v1→v2 migration guide: every renamed method, removed feature, behavioral change, and the full 28-item LLM mistakes catalogue (§ 9)references/validators-serializers.md— Complete validator and serializer reference: all 4 modes, Annotated vs decorator patterns, model validators, computed fields, @validate_callreferences/advanced-features.md— TypeAdapter, custom types, generic models, dynamic models, BaseSettings, discriminated unions, aliases, JSON Schema customization, dataclasses