mirror of
https://github.com/fastapi/sqlmodel.git
synced 2025-12-30 22:20:06 -06:00
📝 Update all docs references to Optional to use the new syntax in Python 3.10, e.g. int | None (#1351)
This commit is contained in:
committed by
GitHub
parent
0e5e19773c
commit
61523864f1
@@ -4,7 +4,7 @@ In the previous chapter, we saw how to add rows to the database using **SQLModel
|
||||
|
||||
Now let's talk a bit about why the `id` field **can't be `NULL`** on the database because it's a **primary key**, and we declare it using `Field(primary_key=True)`.
|
||||
|
||||
But the same `id` field actually **can be `None`** in the Python code, so we declare the type with `int | None (or Optional[int])`, and set the default value to `Field(default=None)`:
|
||||
But the same `id` field actually **can be `None`** in the Python code, so we declare the type with `int | None`, and set the default value to `Field(default=None)`:
|
||||
|
||||
{* ./docs_src/tutorial/automatic_id_none_refresh/tutorial001_py310.py ln[4:8] hl[5] *}
|
||||
|
||||
@@ -18,15 +18,15 @@ When we create a new `Hero` instance, we don't set the `id`:
|
||||
|
||||
{* ./docs_src/tutorial/automatic_id_none_refresh/tutorial001_py310.py ln[21:24] hl[21:24] *}
|
||||
|
||||
### How `Optional` Helps
|
||||
### How `int | None` Helps
|
||||
|
||||
Because we don't set the `id`, it takes the Python's default value of `None` that we set in `Field(default=None)`.
|
||||
|
||||
This is the only reason why we define it with `Optional` and with a default value of `None`.
|
||||
This is the only reason why we define it with `int | None` and with a default value of `None`.
|
||||
|
||||
Because at this point in the code, **before interacting with the database**, the Python value could actually be `None`.
|
||||
|
||||
If we assumed that the `id` was *always* an `int` and added the type annotation without `Optional`, we could end up writing broken code, like:
|
||||
If we assumed that the `id` was *always* an `int` and added the type annotation without `int | None`, we could end up writing broken code, like:
|
||||
|
||||
```Python
|
||||
next_hero_id = hero_1.id + 1
|
||||
@@ -38,7 +38,7 @@ If we ran this code before saving the hero to the database and the `hero_1.id` w
|
||||
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
|
||||
```
|
||||
|
||||
But by declaring it with `Optional[int]`, the editor will help us to avoid writing broken code by showing us a warning telling us that the code could be invalid if `hero_1.id` is `None`. 🔍
|
||||
But by declaring it with `int | None`, the editor will help us to avoid writing broken code by showing us a warning telling us that the code could be invalid if `hero_1.id` is `None`. 🔍
|
||||
|
||||
## Print the Default `id` Values
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@ WHERE team.id = ?
|
||||
INFO Engine [cached since 0.001795s ago] (1,)
|
||||
```
|
||||
|
||||
There's something else to note. We marked `team_id` as `Optional[int]`, meaning that this could be `NULL` on the database (and `None` in Python).
|
||||
There's something else to note. We marked `team_id` as `int | None`, meaning that this could be `NULL` on the database (and `None` in Python).
|
||||
|
||||
That means that a hero doesn't have to have a team. And in this case, **Spider-Boy** doesn't have one.
|
||||
|
||||
|
||||
@@ -67,11 +67,9 @@ And the type of each of them will also be the type of table column:
|
||||
|
||||
Let's now see with more detail these field/column declarations.
|
||||
|
||||
### Optional Fields, Nullable Columns
|
||||
### `None` Fields, Nullable Columns
|
||||
|
||||
Let's start with `age`, notice that it has a type of `int | None (or Optional[int])`.
|
||||
|
||||
And we import that `Optional` from the `typing` standard module.
|
||||
Let's start with `age`, notice that it has a type of `int | None`.
|
||||
|
||||
That is the standard way to declare that something "could be an `int` or `None`" in Python.
|
||||
|
||||
@@ -81,21 +79,23 @@ And we also set the default value of `age` to `None`.
|
||||
|
||||
/// tip
|
||||
|
||||
We also define `id` with `Optional`. But we will talk about `id` below.
|
||||
We also define `id` with `int | None`. But we will talk about `id` below.
|
||||
|
||||
///
|
||||
|
||||
This way, we tell **SQLModel** that `age` is not required when validating data and that it has a default value of `None`.
|
||||
Because the type is `int | None`:
|
||||
|
||||
And we also tell it that, in the SQL database, the default value of `age` is `NULL` (the SQL equivalent to Python's `None`).
|
||||
* When validating data, `None` will be an allowed value for `age`.
|
||||
* In the database, the column for `age` will be allowed to have `NULL` (the SQL equivalent to Python's `None`).
|
||||
|
||||
So, this column is "nullable" (can be set to `NULL`).
|
||||
And because there's a default value `= None`:
|
||||
|
||||
/// info
|
||||
* When validating data, this `age` field won't be required, it will be `None` by default.
|
||||
* When saving to the database, the `age` column will have a `NULL` value by default.
|
||||
|
||||
In terms of **Pydantic**, `age` is an **optional field**.
|
||||
/// tip
|
||||
|
||||
In terms of **SQLAlchemy**, `age` is a **nullable column**.
|
||||
The default value could have been something else, like `= 42`.
|
||||
|
||||
///
|
||||
|
||||
@@ -111,7 +111,7 @@ To do that, we use the special `Field` function from `sqlmodel` and set the argu
|
||||
|
||||
That way, we tell **SQLModel** that this `id` field/column is the primary key of the table.
|
||||
|
||||
But inside the SQL database, it is **always required** and can't be `NULL`. Why should we declare it with `Optional`?
|
||||
But inside the SQL database, it is **always required** and can't be `NULL`. Why should we declare it with `int | None`?
|
||||
|
||||
The `id` will be required in the database, but it will be *generated by the database*, not by our code.
|
||||
|
||||
@@ -128,7 +128,7 @@ somehow_save_in_db(my_hero)
|
||||
do_something(my_hero.id) # Now my_hero.id has a value generated in DB 🎉
|
||||
```
|
||||
|
||||
So, because in *our code* (not in the database) the value of `id` *could be* `None`, we use `Optional`. This way **the editor will be able to help us**, for example, if we try to access the `id` of an object that we haven't saved in the database yet and would still be `None`.
|
||||
So, because in *our code* (not in the database) the value of `id` *could be* `None`, we use `int | None`. This way **the editor will be able to help us**, for example, if we try to access the `id` of an object that we haven't saved in the database yet and would still be `None`.
|
||||
|
||||
<img class="shadow" src="/img/create-db-and-table/inline-errors01.png">
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ For input, we have:
|
||||
|
||||
If we pay attention, it shows that the client *could* send an `id` in the JSON body of the request.
|
||||
|
||||
This means that the client could try to use the same ID that already exists in the database for another hero.
|
||||
This means that the client could try to use the same ID that already exists in the database to create another hero.
|
||||
|
||||
That's not what we want.
|
||||
|
||||
@@ -51,7 +51,7 @@ The `age` is optional, we don't have to return it, or it could be `None` (or `nu
|
||||
|
||||
Here's the weird thing, the `id` currently seems also "optional". 🤔
|
||||
|
||||
This is because in our **SQLModel** class we declare the `id` with `Optional[int]`, because it could be `None` in memory until we save it in the database and we finally get the actual ID.
|
||||
This is because in our **SQLModel** class we declare the `id` with a default value of `= None`, because it could be `None` in memory until we save it in the database and we finally get the actual ID.
|
||||
|
||||
But in the responses, we always send a model from the database, so it **always has an ID**. So the `id` in the responses can be declared as required.
|
||||
|
||||
@@ -71,7 +71,7 @@ And in most of the cases, the developer of the client for that API **will also b
|
||||
|
||||
### So Why is it Important to Have Required IDs
|
||||
|
||||
Now, what's the matter with having one **`id` field marked as "optional"** in a response when in reality it is always required?
|
||||
Now, what's the matter with having one **`id` field marked as "optional"** in a response when in reality it is always available (required)?
|
||||
|
||||
For example, **automatically generated clients** in other languages (or also in Python) would have some declaration that this field `id` is optional.
|
||||
|
||||
@@ -98,7 +98,7 @@ But we also want to have a `HeroCreate` for the data we want to receive when **c
|
||||
* `secret_name`, required
|
||||
* `age`, optional
|
||||
|
||||
And we want to have a `HeroPublic` with the `id` field, but this time annotated with `id: int`, instead of `id: Optional[int]`, to make it clear that it is required in responses **read** from the clients:
|
||||
And we want to have a `HeroPublic` with the `id` field, but this time with a type of `id: int`, instead of `id: int | None`, to make it clear that it will always have an `int` in responses **read** from the clients:
|
||||
|
||||
* `id`, required
|
||||
* `name`, required
|
||||
@@ -225,7 +225,7 @@ Let's start with the only **table model**, the `Hero`:
|
||||
|
||||
Notice that `Hero` now doesn't inherit from `SQLModel`, but from `HeroBase`.
|
||||
|
||||
And now we only declare one single field directly, the `id`, that here is `Optional[int]`, and is a `primary_key`.
|
||||
And now we only declare one single field directly, the `id`, that here is `int | None`, and is a `primary_key`.
|
||||
|
||||
And even though we don't declare the other fields **explicitly**, because they are inherited, they are also part of this `Hero` model.
|
||||
|
||||
|
||||
@@ -8,15 +8,15 @@ We want clients to be able to update the `name`, the `secret_name`, and the `age
|
||||
|
||||
But we don't want them to have to include all the data again just to **update a single field**.
|
||||
|
||||
So, we need to have all those fields **marked as optional**.
|
||||
So, we need to make all those fields **optional**.
|
||||
|
||||
And because the `HeroBase` has some of them as *required* and not optional, we will need to **create a new model**.
|
||||
And because the `HeroBase` has some of them *required* (without a default value), we will need to **create a new model**.
|
||||
|
||||
/// tip
|
||||
|
||||
Here is one of those cases where it probably makes sense to use an **independent model** instead of trying to come up with a complex tree of models inheriting from each other.
|
||||
|
||||
Because each field is **actually different** (we just change it to `Optional`, but that's already making it different), it makes sense to have them in their own model.
|
||||
Because each field is **actually different** (we just set a default value of `None`, but that's already making it different), it makes sense to have them in their own model.
|
||||
|
||||
///
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ We **removed** the previous `team_id` field (column) because now the relationshi
|
||||
|
||||
The relationship attribute is now named **`teams`** instead of `team`, as now we support multiple teams.
|
||||
|
||||
It is no longer an `Optional[Team]` but a list of teams, annotated as **`list[Team]`**.
|
||||
It no longer has a type of `Team | None` but a list of teams, the type is now declared as **`list[Team]`**.
|
||||
|
||||
We are using the **`Relationship()`** here too.
|
||||
|
||||
|
||||
@@ -68,15 +68,15 @@ if hero.team:
|
||||
print(hero.team.name)
|
||||
```
|
||||
|
||||
## Optional Relationship Attributes
|
||||
## Relationship Attributes or `None`
|
||||
|
||||
Notice that in the `Hero` class, the type annotation for `team` is `Optional[Team]`.
|
||||
Notice that in the `Hero` class, the type annotation for `team` is `Team | None`.
|
||||
|
||||
This means that this attribute could be `None`, or it could be a full `Team` object.
|
||||
|
||||
This is because the related **`team_id` could also be `None`** (or `NULL` in the database).
|
||||
|
||||
If it was required for a `Hero` instance to belong to a `Team`, then the `team_id` would be `int` instead of `Optional[int]`, its `Field` would be `Field(foreign_key="team.id")` instead of `Field(default=None, foreign_key="team.id")` and the `team` attribute would be a `Team` instead of `Optional[Team]`.
|
||||
If it was required for a `Hero` instance to belong to a `Team`, then the `team_id` would be `int` instead of `int | None`, its `Field` would be `Field(foreign_key="team.id")` instead of `Field(default=None, foreign_key="team.id")` and the `team` attribute would be a `Team` instead of `Team | None`.
|
||||
|
||||
## Relationship Attributes With Lists
|
||||
|
||||
|
||||
@@ -690,7 +690,7 @@ It would be an error telling you that
|
||||
|
||||
> `Hero.age` is potentially `None`, and you cannot compare `None` with `>`
|
||||
|
||||
This is because as we are using pure and plain Python annotations for the fields, `age` is indeed annotated as `int | None (or Optional[int])`.
|
||||
This is because as we are using pure and plain Python annotations for the fields, `age` is indeed annotated as `int | None`.
|
||||
|
||||
By using this simple and standard Python type annotations we get the benefit of the extra simplicity and the inline error checks when creating or using instances. ✨
|
||||
|
||||
|
||||
Reference in New Issue
Block a user