Migrate from Pydantic v1 to Pydantic v2¶
If you have an old FastAPI app, you might be using Pydantic version 1.
FastAPI has had support for either Pydantic v1 or v2 since version 0.100.0.
If you had installed Pydantic v2, it would use it. If instead you had Pydantic v1, it would use that.
Pydantic v1 is now deprecated and support for it will be removed in the next versions of FastAPI, you should migrate to Pydantic v2. This way you will get the latest features, improvements, and fixes.
Warning
Also, the Pydantic team stopped support for Pydantic v1 for the latest versions of Python, starting with Python 3.14.
If you want to use the latest features of Python, you will need to make sure you use Pydantic v2.
If you have an old FastAPI app with Pydantic v1, here I'll show you how to migrate it to Pydantic v2, and the new features in FastAPI 0.119.0 to help you with a gradual migration.
Official Guide¶
Pydantic has an official Migration Guide from v1 to v2.
It also includes what has changed, how validations are now more correct and strict, possible caveats, etc.
You can read it to understand better what has changed.
Tests¶
Make sure you have tests for your app and you run them on continuous integration (CI).
This way, you can do the upgrade and make sure everything is still working as expected.
bump-pydantic
¶
In many cases, when you use regular Pydantic models without customizations, you will be able to automate most of the process of migrating from Pydantic v1 to Pydantic v2.
You can use bump-pydantic
from the same Pydantic team.
This tool will help you to automatically change most of the code that needs to be changed.
After this, you can run the tests and check if everything works. If it does, you are done. 😎
Pydantic v1 in v2¶
Pydantic v2 includes everything from Pydantic v1 as a submodule pydantic.v1
.
This means that you can install the latest version of Pydantic v2 and import and use the old Pydantic v1 components from this submodule, as if you had the old Pydantic v1 installed.
from pydantic.v1 import BaseModel
class Item(BaseModel):
name: str
description: str | None = None
size: float
🤓 Other versions and variants
from typing import Union
from pydantic.v1 import BaseModel
class Item(BaseModel):
name: str
description: Union[str, None] = None
size: float
FastAPI support for Pydantic v1 in v2¶
Since FastAPI 0.119.0, there's also partial support for Pydantic v1 from inside of Pydantic v2, to facilitate the migration to v2.
So, you could upgrade Pydantic to the latest version 2, and change the imports to use the pydantic.v1
submodule, and in many cases it would just work.
from fastapi import FastAPI
from pydantic.v1 import BaseModel
class Item(BaseModel):
name: str
description: str | None = None
size: float
app = FastAPI()
@app.post("/items/")
async def create_item(item: Item) -> Item:
return item
🤓 Other versions and variants
from typing import Union
from fastapi import FastAPI
from pydantic.v1 import BaseModel
class Item(BaseModel):
name: str
description: Union[str, None] = None
size: float
app = FastAPI()
@app.post("/items/")
async def create_item(item: Item) -> Item:
return item
Warning
Have in mind that as the Pydantic team no longer supports Pydantic v1 in recent versions of Python, starting from Python 3.14, using pydantic.v1
is also not supported in Python 3.14 and above.
Pydantic v1 and v2 on the same app¶
It's not supported by Pydantic to have a model of Pydantic v2 with its own fields defined as Pydantic v1 models or vice versa.
graph TB
subgraph "❌ Not Supported"
direction TB
subgraph V2["Pydantic v2 Model"]
V1Field["Pydantic v1 Model"]
end
subgraph V1["Pydantic v1 Model"]
V2Field["Pydantic v2 Model"]
end
end
style V2 fill:#f9fff3
style V1 fill:#fff6f0
style V1Field fill:#fff6f0
style V2Field fill:#f9fff3
...but, you can have separated models using Pydantic v1 and v2 in the same app.
graph TB
subgraph "✅ Supported"
direction TB
subgraph V2["Pydantic v2 Model"]
V2Field["Pydantic v2 Model"]
end
subgraph V1["Pydantic v1 Model"]
V1Field["Pydantic v1 Model"]
end
end
style V2 fill:#f9fff3
style V1 fill:#fff6f0
style V1Field fill:#fff6f0
style V2Field fill:#f9fff3
In some cases, it's even possible to have both Pydantic v1 and v2 models in the same path operation in your FastAPI app:
from fastapi import FastAPI
from pydantic import BaseModel as BaseModelV2
from pydantic.v1 import BaseModel
class Item(BaseModel):
name: str
description: str | None = None
size: float
class ItemV2(BaseModelV2):
name: str
description: str | None = None
size: float
app = FastAPI()
@app.post("/items/", response_model=ItemV2)
async def create_item(item: Item):
return item
🤓 Other versions and variants
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel as BaseModelV2
from pydantic.v1 import BaseModel
class Item(BaseModel):
name: str
description: Union[str, None] = None
size: float
class ItemV2(BaseModelV2):
name: str
description: Union[str, None] = None
size: float
app = FastAPI()
@app.post("/items/", response_model=ItemV2)
async def create_item(item: Item):
return item
In this example above, the input model is a Pydantic v1 model, and the output model (defined in response_model=ItemV2
) is a Pydantic v2 model.
Pydantic v1 parameters¶
If you need to use some of the FastAPI-specific tools for parameters like Body
, Query
, Form
, etc. with Pydantic v1 models, you can import them from fastapi.temp_pydantic_v1_params
while you finish the migration to Pydantic v2:
from typing import Annotated
from fastapi import FastAPI
from fastapi.temp_pydantic_v1_params import Body
from pydantic.v1 import BaseModel
class Item(BaseModel):
name: str
description: str | None = None
size: float
app = FastAPI()
@app.post("/items/")
async def create_item(item: Annotated[Item, Body(embed=True)]) -> Item:
return item
🤓 Other versions and variants
from typing import Annotated, Union
from fastapi import FastAPI
from fastapi.temp_pydantic_v1_params import Body
from pydantic.v1 import BaseModel
class Item(BaseModel):
name: str
description: Union[str, None] = None
size: float
app = FastAPI()
@app.post("/items/")
async def create_item(item: Annotated[Item, Body(embed=True)]) -> Item:
return item
from typing import Union
from fastapi import FastAPI
from fastapi.temp_pydantic_v1_params import Body
from pydantic.v1 import BaseModel
from typing_extensions import Annotated
class Item(BaseModel):
name: str
description: Union[str, None] = None
size: float
app = FastAPI()
@app.post("/items/")
async def create_item(item: Annotated[Item, Body(embed=True)]) -> Item:
return item
Migrate in steps¶
Tip
First try with bump-pydantic
, if your tests pass and that works, then you're done in one command. ✨
If bump-pydantic
doesn't work for your use case, you can use the support for both Pydantic v1 and v2 models in the same app to do the migration to Pydantic v2 gradually.
You could fist upgrade Pydantic to use the latest version 2, and change the imports to use pydantic.v1
for all your models.
Then, you can start migrating your models from Pydantic v1 to v2 in groups, in gradual steps. 🚶