Pydantic v2 is a ground-up rewrite with a Rust core, and it shows - validation is noticeably faster. But the migration isn’t trivial. I spent about a day migrating a medium-sized FastAPI project, and here’s what I learned.
Use bump-pydantic first Link to heading
Before doing anything manual, run the automated migration tool:
pip install bump-pydantic
bump-pydantic .
This handles about 80% of the changes automatically. It renames methods, updates imports, and converts the obvious stuff. Review the diff carefully though - it occasionally makes mistakes with complex validators.
What bump-pydantic misses Link to heading
The tool doesn’t catch everything. Here’s what I had to fix manually:
Validator signature changes Link to heading
@validator becomes @field_validator, but the signature also changes:
# v1
@validator('name')
def validate_name(cls, v, values):
return v.strip()
# v2 - note the @classmethod and changed signature
@field_validator('name')
@classmethod
def validate_name(cls, v: str) -> str:
return v.strip()
If you need access to other fields, use @model_validator instead:
@model_validator(mode='after')
def validate_model(self) -> Self:
# self.field1, self.field2 etc are available
return self
Config class Link to heading
class Config becomes model_config:
# v1
class Model(BaseModel):
class Config:
orm_mode = True
# v2
class Model(BaseModel):
model_config = ConfigDict(from_attributes=True)
Note that orm_mode was renamed to from_attributes.
Method renames Link to heading
These are straightforward but easy to miss in string searches:
| v1 | v2 |
|---|---|
.dict() | .model_dump() |
.json() | .model_dump_json() |
.parse_obj() | .model_validate() |
.parse_raw() | .model_validate_json() |
__fields__ | model_fields |
Field constraints Link to heading
Field(regex=...) becomes Field(pattern=...):
# v1
name: str = Field(regex=r'^[a-z]+$')
# v2
name: str = Field(pattern=r'^[a-z]+$')
Type annotation changes Link to heading
v2 prefers the modern union syntax:
# v1 style (still works but deprecated)
from typing import Optional
name: Optional[str] = None
# v2 style
name: str | None = None
Performance Link to heading
The Rust core makes a real difference. In my benchmarks on a model with nested objects and several validators, v2 was about 5-10x faster for validation. Serialisation (.model_dump()) was roughly 2x faster.
Worth the migration effort? Absolutely, especially if you’re validating lots of data.
For testing Pydantic models, see pytest tips.
Further reading Link to heading
- Official migration guide - comprehensive reference
- bump-pydantic - the automated migration tool
- What’s new in v2 - explains the reasoning behind changes