Cheatsheet for migrating Pydantic v1 code to v2.
Method renames
| v1 | v2 |
|---|---|
parse_obj(data) | model_validate(data) |
parse_raw(json_str) | model_validate_json(json_str) |
parse_file(path) | model_validate_json(Path(...).read_text()) |
dict() | model_dump() |
json() | model_dump_json() |
copy() | model_copy() |
construct() | model_construct() |
update_forward_refs() | model_rebuild() |
Config (class) | model_config (dict) |
Config
v1:
class Config:
orm_mode = True
allow_population_by_field_name = True
use_enum_values = True
v2:
model_config = {
"from_attributes": True, # renamed from orm_mode
"populate_by_name": True,
"use_enum_values": True,
}
Validators
v1:
from pydantic import validator, root_validator
@validator("name")
def name_must_be_long(cls, v):
return v
@root_validator
def check(cls, values):
return values
v2:
from pydantic import field_validator, model_validator
@field_validator("name")
@classmethod
def name_must_be_long(cls, v: str) -> str:
return v
@model_validator(mode="after")
def check(self):
return self
Always @classmethod decorator on field_validator. model_validator(mode="after") takes / returns self.
Field
v1:
field: int = Field(..., min_value=0)
v2:
field: int = Field(..., ge=0)
Renamed: min_value → ge, max_value → le, gt, lt same.
Optional / nullable
Mostly the same, but Mapped[int] semantics tightened in v2.
ValidationError
v1: from pydantic import ValidationError (same import).
v2: error structure differs. loc is now a tuple; type is a string code; msg is the message.
Models with __init__
v1: subclassing with custom __init__ worked.
v2: prefer model_validator(mode="before") instead of overriding __init__.
from_orm
v1: User.from_orm(sa_user).
v2: User.model_validate(sa_user) with from_attributes=True.
fields
v1: Model.__fields__ is a dict.
v2: Model.model_fields is a dict of FieldInfo.
Settings
v1: from pydantic import BaseSettings.
v2: from pydantic_settings import BaseSettings (separate package).
uv add pydantic-settings
Config also moves:
# v2
from pydantic_settings import SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(env_file=".env", env_prefix="MYAPP_")
Json type
v1: Json[T] and Json.
v2: Json[T] works; standalone Json removed.
Strict types
v1: StrictInt, StrictStr, etc. (still exist).
v2: prefer Annotated[int, Strict()] syntax.
Aliases
v1: Field(alias="...").
v2: same, plus validation_alias, serialization_alias, AliasChoices, AliasPath.
TypeAdapter
v1: parse_obj_as(List[User], data).
v2: TypeAdapter(list[User]).validate_python(data).
Generics
v1: BaseModel, GenericModel.
v2: BaseModel, Generic[T] (no separate GenericModel).
# v2
from typing import TypeVar, Generic
T = TypeVar("T")
class Page(BaseModel, Generic[T]):
items: list[T]
RootModel
v1: __root__ field.
v2: RootModel[T] class.
class Names(RootModel[list[str]]):
pass
Codemod: bump-pydantic
uv pip install bump-pydantic
bump-pydantic src/
Auto-applies many renames. Not perfect; review changes.
Compatibility shim
from pydantic.v1 import BaseModel as V1BaseModel
For gradual migration. v1 BaseModel coexists.
What might break
- Field ordering for
model_validatorcalls. - Strict mode default changed (some types stricter).
__init_subclass__interactions.- Custom validators relying on v1 internals.
Testing the migration
pytest -x
mypy src
ruff check src
Plus integration tests against your real flows. Common breakage: ORM conversion (from_orm → model_validate + from_attributes).
Common mistakes
- Mixing v1 and v2 code without
pydantic.v1shim. - Skipping codemod review — silent wrong changes.
- Forgetting
populate_by_name=Trueafter rename. - Not updating FastAPI version (FastAPI ≥ 0.100 for Pydantic v2).
You finished
20 Pydantic cheatsheets done. Pair with:
If you want my v1→v2 migration template + codemod script, it’s at rajpoot.dev .
Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .