Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions .github/DISCUSSION_TEMPLATE/questions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
22 changes: 8 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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")
Expand All @@ -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")
Expand Down
4 changes: 2 additions & 2 deletions docs/db-to-code.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Binary file modified docs/img/index/autocompletion01.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/img/index/autocompletion02.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/img/index/inline-errors01.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 8 additions & 14 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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")
Expand All @@ -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")
Expand Down
10 changes: 5 additions & 5 deletions docs/tutorial/automatic-id-none-refresh.md
Original file line number Diff line number Diff line change
Expand Up @@ -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] *}

Expand All @@ -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
Expand All @@ -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

Expand Down
2 changes: 1 addition & 1 deletion docs/tutorial/connect/create-connected-rows.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
26 changes: 13 additions & 13 deletions docs/tutorial/create-db-and-table.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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`.

///

Expand All @@ -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.

Expand All @@ -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">

Expand Down
10 changes: 5 additions & 5 deletions docs/tutorial/fastapi/multiple-models.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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.

Expand All @@ -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.

Expand All @@ -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
Expand Down Expand Up @@ -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.

Expand Down
6 changes: 3 additions & 3 deletions docs/tutorial/fastapi/update.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

///

Expand Down
2 changes: 1 addition & 1 deletion docs/tutorial/many-to-many/create-models-with-link.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion docs/tutorial/where.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. ✨

Expand Down