]> git.ipfire.org Git - thirdparty/fastapi/sqlmodel.git/commitdiff
📝 Update all docs references to `Optional` to use the new syntax in Python 3.10,...
authorSebastián Ramírez <tiangolo@gmail.com>
Sun, 27 Apr 2025 18:53:37 +0000 (20:53 +0200)
committerGitHub <noreply@github.com>
Sun, 27 Apr 2025 18:53:37 +0000 (20:53 +0200)
15 files changed:
.github/DISCUSSION_TEMPLATE/questions.yml
README.md
docs/db-to-code.md
docs/img/index/autocompletion01.png
docs/img/index/autocompletion02.png
docs/img/index/inline-errors01.png
docs/index.md
docs/tutorial/automatic-id-none-refresh.md
docs/tutorial/connect/create-connected-rows.md
docs/tutorial/create-db-and-table.md
docs/tutorial/fastapi/multiple-models.md
docs/tutorial/fastapi/update.md
docs/tutorial/many-to-many/create-models-with-link.md
docs/tutorial/relationship-attributes/define-relationships-attributes.md
docs/tutorial/where.md

index 97902a658e88a59306b3ef1ae09524af8e331d97..524d5c2d0805315d990fa6052ad76471bd4228f7 100644 (file)
@@ -64,16 +64,14 @@ body:
         If I (or someone) can copy it, run it, and see it right away, there's a much higher chance I (or someone) will be able to help you.
 
       placeholder: |
-        from typing import Optional
-
         from sqlmodel import Field, Session, SQLModel, create_engine
 
 
         class Hero(SQLModel, table=True):
-            id: Optional[int] = Field(default=None, primary_key=True)
+            id: int | None = Field(default=None, primary_key=True)
             name: str
             secret_name: str
-            age: Optional[int] = None
+            age: int | None = None
 
 
         hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
index f8a32a36345e4138a1ab4acfa2195fdcea733340..712167ffd6ffdccf1d366502a4a2a6db03b0bd2e 100644 (file)
--- a/README.md
+++ b/README.md
@@ -105,16 +105,14 @@ And you want it to have this data:
 Then you could create a **SQLModel** model like this:
 
 ```Python
-from typing import Optional
-
 from sqlmodel import Field, SQLModel
 
 
 class Hero(SQLModel, table=True):
-    id: Optional[int] = Field(default=None, primary_key=True)
+    id: int | None = Field(default=None, primary_key=True)
     name: str
     secret_name: str
-    age: Optional[int] = None
+    age: int | None = None
 ```
 
 That class `Hero` is a **SQLModel** model, the equivalent of a SQL table in Python code.
@@ -149,17 +147,15 @@ And **inline errors**:
 
 You can learn a lot more about **SQLModel** by quickly following the **tutorial**, but if you need a taste right now of how to put all that together and save to the database, you can do this:
 
-```Python hl_lines="18  21  23-27"
-from typing import Optional
-
+```Python hl_lines="16  19  21-25"
 from sqlmodel import Field, Session, SQLModel, create_engine
 
 
 class Hero(SQLModel, table=True):
-    id: Optional[int] = Field(default=None, primary_key=True)
+    id: int | None = Field(default=None, primary_key=True)
     name: str
     secret_name: str
-    age: Optional[int] = None
+    age: int | None = None
 
 
 hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
@@ -185,17 +181,15 @@ That will save a **SQLite** database with the 3 heroes.
 
 Then you could write queries to select from that same database, for example with:
 
-```Python hl_lines="15-18"
-from typing import Optional
-
+```Python hl_lines="13-17"
 from sqlmodel import Field, Session, SQLModel, create_engine, select
 
 
 class Hero(SQLModel, table=True):
-    id: Optional[int] = Field(default=None, primary_key=True)
+    id: int | None = Field(default=None, primary_key=True)
     name: str
     secret_name: str
-    age: Optional[int] = None
+    age: int | None = None
 
 
 engine = create_engine("sqlite:///database.db")
index 3d289d75fa77c7b7b56b5d129983b512009a5792..53a8d35886435391a74f55d00f1e603fc8966205 100644 (file)
@@ -252,10 +252,10 @@ For example this class is part of that **Object** Oriented Programming:
 
 ```Python
 class Hero(SQLModel):
-    id: Optional[int] = Field(default=None, primary_key=True)
+    id: int | None = Field(default=None, primary_key=True)
     name: str
     secret_name: str
-    age: Optional[int] = None
+    age: int | None = None
 ```
 
 * **Relational**: refers to the **SQL Databases**. Remember that they are also called **Relational Databases**, because each of those tables is also called a "**relation**"? That's where the "**Relational**" comes from.
index 1a47940b3c03fde8099c65dceb7d2d8ab52473f9..cba2649fe9ed532e1b9d654f7f8a5ab2b1c5245c 100644 (file)
Binary files a/docs/img/index/autocompletion01.png and b/docs/img/index/autocompletion01.png differ
index effa22ee85888001c2bd34644b92c880b9425765..5918e1d2002e348e682551e22425db3a879ab8d5 100644 (file)
Binary files a/docs/img/index/autocompletion02.png and b/docs/img/index/autocompletion02.png differ
index f5ef90ed714ea74a9acf0809fee0310fc011a597..b689c55624619fd9123c3fd377bb9745411115b6 100644 (file)
Binary files a/docs/img/index/inline-errors01.png and b/docs/img/index/inline-errors01.png differ
index 0feec81c69404b2e7974fe1d078bc226bb35a46c..cd48b91385a3f1bf1a5cf8ef2395f4bf57bfbefd 100644 (file)
@@ -118,16 +118,14 @@ And you want it to have this data:
 Then you could create a **SQLModel** model like this:
 
 ```Python
-from typing import Optional
-
 from sqlmodel import Field, SQLModel
 
 
 class Hero(SQLModel, table=True):
-    id: Optional[int] = Field(default=None, primary_key=True)
+    id: int | None = Field(default=None, primary_key=True)
     name: str
     secret_name: str
-    age: Optional[int] = None
+    age: int | None = None
 ```
 
 That class `Hero` is a **SQLModel** model, the equivalent of a SQL table in Python code.
@@ -162,17 +160,15 @@ And **inline errors**:
 
 You can learn a lot more about **SQLModel** by quickly following the **tutorial**, but if you need a taste right now of how to put all that together and save to the database, you can do this:
 
-```Python hl_lines="18  21  23-27"
-from typing import Optional
-
+```Python hl_lines="16  19  21-25"
 from sqlmodel import Field, Session, SQLModel, create_engine
 
 
 class Hero(SQLModel, table=True):
-    id: Optional[int] = Field(default=None, primary_key=True)
+    id: int | None = Field(default=None, primary_key=True)
     name: str
     secret_name: str
-    age: Optional[int] = None
+    age: int | None = None
 
 
 hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
@@ -198,17 +194,15 @@ That will save a **SQLite** database with the 3 heroes.
 
 Then you could write queries to select from that same database, for example with:
 
-```Python hl_lines="15-18"
-from typing import Optional
-
+```Python hl_lines="13-17"
 from sqlmodel import Field, Session, SQLModel, create_engine, select
 
 
 class Hero(SQLModel, table=True):
-    id: Optional[int] = Field(default=None, primary_key=True)
+    id: int | None = Field(default=None, primary_key=True)
     name: str
     secret_name: str
-    age: Optional[int] = None
+    age: int | None = None
 
 
 engine = create_engine("sqlite:///database.db")
index 7c7983c3d38817ecafbd553c6fec74aecf7a56ea..0e67633dee625bed06a9f3213862301c95b01e8c 100644 (file)
@@ -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
 
index e91c351a166c553d13bdc502ad838121455eb5fd..b01d20eb2bedcfe83c04fb9985122ad358f30f91 100644 (file)
@@ -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.
 
index 288e2bb20b8871f623d26dd3f1fb91a3702d0f4a..2f2f34c828794ba68cf472f4910fbe2ade5c227f 100644 (file)
@@ -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">
 
index 301c958d3feab4e616a29b36663cdf1a5440025c..1bc045612bda396cf9743accf713042222708814 100644 (file)
@@ -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.
 
index 0aed115d6dce27f0935e13abfb8f18f2787b081c..e3c8ac6ac79f2e2f9840263b0294d8719a431e29 100644 (file)
@@ -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.
 
 ///
 
index 36a0e10e7f1846de4c46c8abd28f0a9818faaa46..587fa436a8e8aa1f6423e35678e4823e1e1edf5e 100644 (file)
@@ -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.
 
index 2646082c383e980cf54854d6206575ee1664ecfa..c5307d3663ac0adb079fcaea1e76371048c0ace2 100644 (file)
@@ -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
 
index 7e0fe97c4c7f030c8186b8c0dec7ec9c9dd5db33..b6d08e72fa74053d15311374a5ecef5cd73fad71 100644 (file)
@@ -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. ✨