Add support for Pydantic v2 (while keeping support for v1 if v2 is not available), including initial work by AntonDeMeester (#722)

Co-authored-by: Mohamed Farahat <farahats9@yahoo.com>
Co-authored-by: Stefan Borer <stefan.borer@gmail.com>
Co-authored-by: Peter Landry <peter.landry@gmail.com>
Co-authored-by: Anton De Meester <antondemeester+github@gmail.com>
This commit is contained in:
Sebastián Ramírez
2023-12-04 15:42:39 +01:00
committed by GitHub
parent 5b733b348d
commit fa2f178b8a
79 changed files with 2614 additions and 517 deletions

View File

@ -175,15 +175,17 @@ Now we use the type annotation `HeroCreate` for the request JSON data in the `he
# Code below omitted 👇
```
Then we create a new `Hero` (this is the actual **table** model that saves things to the database) using `Hero.from_orm()`.
Then we create a new `Hero` (this is the actual **table** model that saves things to the database) using `Hero.model_validate()`.
The method `.from_orm()` reads data from another object with attributes and creates a new instance of this class, in this case `Hero`.
The method `.model_validate()` reads data from another object with attributes (or a dict) and creates a new instance of this class, in this case `Hero`.
The alternative is `Hero.parse_obj()` that reads data from a dictionary.
In this case, we have a `HeroCreate` instance in the `hero` variable. This is an object with attributes, so we use `.model_validate()` to read those attributes.
But as in this case, we have a `HeroCreate` instance in the `hero` variable. This is an object with attributes, so we use `.from_orm()` to read those attributes.
/// tip
In versions of **SQLModel** before `0.0.14` you would use the method `.from_orm()`, but it is now deprecated and you should use `.model_validate()` instead.
///
With this, we create a new `Hero` instance (the one for the database) and put it in the variable `db_hero` from the data in the `hero` variable that is the `HeroCreate` instance we received from the request.
We can now create a new `Hero` instance (the one for the database) and put it in the variable `db_hero` from the data in the `hero` variable that is the `HeroCreate` instance we received from the request.
```Python hl_lines="3"
# Code above omitted 👆

View File

@ -90,7 +90,7 @@ So, we need to read the hero from the database, with the **same logic** we used
The `HeroUpdate` model has all the fields with **default values**, because they all have defaults, they are all optional, which is what we want.
But that also means that if we just call `hero.dict()` we will get a dictionary that could potentially have several or all of those values with their defaults, for example:
But that also means that if we just call `hero.model_dump()` we will get a dictionary that could potentially have several or all of those values with their defaults, for example:
```Python
{
@ -102,7 +102,7 @@ But that also means that if we just call `hero.dict()` we will get a dictionary
And then, if we update the hero in the database with this data, we would be removing any existing values, and that's probably **not what the client intended**.
But fortunately Pydantic models (and so SQLModel models) have a parameter we can pass to the `.dict()` method for that: `exclude_unset=True`.
But fortunately Pydantic models (and so SQLModel models) have a parameter we can pass to the `.model_dump()` method for that: `exclude_unset=True`.
This tells Pydantic to **not include** the values that were **not sent** by the client. Saying it another way, it would **only** include the values that were **sent by the client**.
@ -112,7 +112,7 @@ So, if the client sent a JSON with no values:
{}
```
Then the dictionary we would get in Python using `hero.dict(exclude_unset=True)` would be:
Then the dictionary we would get in Python using `hero.model_dump(exclude_unset=True)` would be:
```Python
{}
@ -126,7 +126,7 @@ But if the client sent a JSON with:
}
```
Then the dictionary we would get in Python using `hero.dict(exclude_unset=True)` would be:
Then the dictionary we would get in Python using `hero.model_dump(exclude_unset=True)` would be:
```Python
{
@ -152,6 +152,9 @@ Then we use that to get the data that was actually sent by the client:
///
/// tip
Before SQLModel 0.0.14, the method was called `hero.dict(exclude_unset=True)`, but it was renamed to `hero.model_dump(exclude_unset=True)` to be consistent with Pydantic v2.
## Update the Hero in the Database
Now that we have a **dictionary with the data sent by the client**, we can iterate for each one of the keys and the values, and then we set them in the database hero model `db_hero` using `setattr()`.
@ -208,7 +211,7 @@ So, if the client wanted to intentionally remove the `age` of a hero, they could
}
```
And when getting the data with `hero.dict(exclude_unset=True)`, we would get:
And when getting the data with `hero.model_dump(exclude_unset=True)`, we would get:
```Python
{
@ -226,4 +229,4 @@ These are some of the advantages of Pydantic, that we can use with SQLModel.
## Recap
Using `.dict(exclude_unset=True)` in SQLModel models (and Pydantic models) we can easily update data **correctly**, even in the **edge cases**. 😎
Using `.model_dump(exclude_unset=True)` in SQLModel models (and Pydantic models) we can easily update data **correctly**, even in the **edge cases**. 😎