Skip to content
Open
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
2 changes: 1 addition & 1 deletion docs/tutorial/fastapi/delete.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ We get a `hero_id` from the path parameter and verify if it exists, just as we d

And if we actually find a hero, we just delete it with the **session**.

{* ./docs_src/tutorial/fastapi/delete/tutorial001_py310.py ln[89:97] hl[89:97] *}
{* ./docs_src/tutorial/fastapi/delete/tutorial001_py310.py ln[91:99] hl[91:99] *}

After deleting it successfully, we just return a response of:

Expand Down
2 changes: 1 addition & 1 deletion docs/tutorial/fastapi/limit-and-offset.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ By default, we will return the first results from the database, so `offset` will

And by default, we will return a maximum of `100` heroes, so `limit` will have a default value of `100`.

{* ./docs_src/tutorial/fastapi/limit_and_offset/tutorial001_py310.py ln[1:2,52:56] hl[1,53,55] *}
{* ./docs_src/tutorial/fastapi/limit_and_offset/tutorial001_py310.py ln[3:4,55:59] hl[3,56,58] *}

We want to allow clients to set different `offset` and `limit` values.

Expand Down
20 changes: 10 additions & 10 deletions docs/tutorial/fastapi/multiple-models.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ And we want to have a `HeroPublic` with the `id` field, but this time with a typ

The simplest way to solve it could be to create **multiple models**, each one with all the corresponding fields:

{* ./docs_src/tutorial/fastapi/multiple_models/tutorial001_py310.py ln[5:22] hl[5:9,12:15,18:22] *}
{* ./docs_src/tutorial/fastapi/multiple_models/tutorial001_py310.py ln[7:24] hl[7:11,14:17,20:24] *}

Here's the important detail, and probably the most important feature of **SQLModel**: only `Hero` is declared with `table = True`.

Expand All @@ -131,13 +131,13 @@ Let's now see how to use these new models in the FastAPI application.

Let's first check how is the process to create a hero now:

{* ./docs_src/tutorial/fastapi/multiple_models/tutorial001_py310.py ln[44:51] hl[44:45,47] *}
{* ./docs_src/tutorial/fastapi/multiple_models/tutorial001_py310.py ln[47:54] hl[47:48,50] *}

Let's check that in detail.

Now we use the type annotation `HeroCreate` for the request JSON data in the `hero` parameter of the **path operation function**.

{* ./docs_src/tutorial/fastapi/multiple_models/tutorial001_py310.py ln[45] hl[45] *}
{* ./docs_src/tutorial/fastapi/multiple_models/tutorial001_py310.py ln[48] hl[48] *}

Then we create a new `Hero` (this is the actual **table** model that saves things to the database) using `Hero.model_validate()`.

Expand All @@ -151,15 +151,15 @@ In versions of **SQLModel** before `0.0.14` you would use the method `.from_orm(

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.

{* ./docs_src/tutorial/fastapi/multiple_models/tutorial001_py310.py ln[47] hl[47] *}
{* ./docs_src/tutorial/fastapi/multiple_models/tutorial001_py310.py ln[50] hl[50] *}

Then we just `add` it to the **session**, `commit`, and `refresh` it, and finally, we return the same `db_hero` variable that has the just refreshed `Hero` instance.

Because it is just refreshed, it has the `id` field set with a new ID taken from the database.

And now that we return it, FastAPI will validate the data with the `response_model`, which is a `HeroPublic`:

{* ./docs_src/tutorial/fastapi/multiple_models/tutorial001_py310.py ln[44] hl[44] *}
{* ./docs_src/tutorial/fastapi/multiple_models/tutorial001_py310.py ln[47] hl[47] *}

This will validate that all the data that we promised is there and will remove any data we didn't declare.

Expand Down Expand Up @@ -211,7 +211,7 @@ We can see from above that they all share some **base** fields:

So let's create a **base** model `HeroBase` that the others can inherit from:

{* ./docs_src/tutorial/fastapi/multiple_models/tutorial002_py310.py ln[5:8] hl[5:8] *}
{* ./docs_src/tutorial/fastapi/multiple_models/tutorial002_py310.py ln[7:10] hl[7:10] *}

As you can see, this is *not* a **table model**, it doesn't have the `table = True` config.

Expand All @@ -221,7 +221,7 @@ But now we can create the **other models inheriting from it**, they will all sha

Let's start with the only **table model**, the `Hero`:

{* ./docs_src/tutorial/fastapi/multiple_models/tutorial002_py310.py ln[5:12] hl[11:12] *}
{* ./docs_src/tutorial/fastapi/multiple_models/tutorial002_py310.py ln[7:14] hl[13:14] *}

Notice that `Hero` now doesn't inherit from `SQLModel`, but from `HeroBase`.

Expand All @@ -237,7 +237,7 @@ And those inherited fields will also be in the **autocompletion** and **inline e

Notice that the parent model `HeroBase` is not a **table model**, but still, we can declare `name` and `age` using `Field(index=True)`.

{* ./docs_src/tutorial/fastapi/multiple_models/tutorial002_py310.py ln[5:12] hl[6,8,11] *}
{* ./docs_src/tutorial/fastapi/multiple_models/tutorial002_py310.py ln[7:14] hl[8,10,13] *}

This won't affect this parent **data model** `HeroBase`.

Expand All @@ -249,7 +249,7 @@ Now let's see the `HeroCreate` model that will be used to define the data that w

This is a fun one:

{* ./docs_src/tutorial/fastapi/multiple_models/tutorial002_py310.py ln[5:16] hl[15:16] *}
{* ./docs_src/tutorial/fastapi/multiple_models/tutorial002_py310.py ln[7:18] hl[17:18] *}

What's happening here?

Expand All @@ -269,7 +269,7 @@ Now let's check the `HeroPublic` model.

This one just declares that the `id` field is required when reading a hero from the API, because a hero read from the API will come from the database, and in the database it will always have an ID.

{* ./docs_src/tutorial/fastapi/multiple_models/tutorial002_py310.py ln[5:20] hl[19:20] *}
{* ./docs_src/tutorial/fastapi/multiple_models/tutorial002_py310.py ln[7:22] hl[21:22] *}

## Review the Updated Docs UI

Expand Down
6 changes: 3 additions & 3 deletions docs/tutorial/fastapi/read-one.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ If you need to refresh how *path parameters* work, including their data validati

///

{* ./docs_src/tutorial/fastapi/read_one/tutorial001_py310.py ln[1:2,59:65] hl[59] *}
{* ./docs_src/tutorial/fastapi/read_one/tutorial001_py310.py ln[3:4,62:68] hl[62] *}

For example, to get the hero with ID `2` we would send a `GET` request to:

Expand All @@ -34,15 +34,15 @@ And to use it, we first import `HTTPException` from `fastapi`.

This will let the client know that they probably made a mistake on their side and requested a hero that doesn't exist in the database.

{* ./docs_src/tutorial/fastapi/read_one/tutorial001_py310.py ln[1:2,59:65] hl[1,62:64] *}
{* ./docs_src/tutorial/fastapi/read_one/tutorial001_py310.py ln[3:4,63:68] hl[3,65:67] *}

## Return the Hero

Then, if the hero exists, we return it.

And because we are using the `response_model` with `HeroPublic`, it will be validated, documented, etc.

{* ./docs_src/tutorial/fastapi/read_one/tutorial001_py310.py ln[1:2,59:65] hl[59,65] *}
{* ./docs_src/tutorial/fastapi/read_one/tutorial001_py310.py ln[3:4,62:68] hl[62,68] *}

## Check the Docs UI

Expand Down
8 changes: 4 additions & 4 deletions docs/tutorial/fastapi/relationships.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,13 @@ It's because we declared the `HeroPublic` with only the same base fields of the

And the same way, we declared the `TeamPublic` with only the same base fields of the `TeamBase` plus the `id`. But it doesn't include a field `heroes` for the **relationship attribute**.

{* ./docs_src/tutorial/fastapi/teams/tutorial001_py310.py ln[5:7,20:21,29:34,43:44] hl[5:7,20:21,29:34,43:44] *}
{* ./docs_src/tutorial/fastapi/teams/tutorial001_py310.py ln[7:9,22:23,31:36,45:46] hl[7:9,22:23,31:36,45:46] *}

Now, remember that <a href="https://fastapi.tiangolo.com/tutorial/response-model/" class="external-link" target="_blank">FastAPI uses the `response_model` to validate and **filter** the response data</a>?

In this case, we used `response_model=TeamPublic` and `response_model=HeroPublic`, so FastAPI will use them to filter the response data, even if we return a **table model** that includes **relationship attributes**:

{* ./docs_src/tutorial/fastapi/teams/tutorial001_py310.py ln[102:107,155:160] hl[102,107,155,160] *}
{* ./docs_src/tutorial/fastapi/teams/tutorial001_py310.py ln[105:110,158:163] hl[105,110,158,163] *}

## Don't Include All the Data

Expand Down Expand Up @@ -132,7 +132,7 @@ Let's add the models `HeroPublicWithTeam` and `TeamPublicWithHeroes`.

We'll add them **after** the other models so that we can easily reference the previous models.

{* ./docs_src/tutorial/fastapi/relationships/tutorial001_py310.py ln[59:64] hl[59:60,63:64] *}
{* ./docs_src/tutorial/fastapi/relationships/tutorial001_py310.py ln[61:66] hl[61:62,65:66] *}

These two models are very **simple in code**, but there's a lot happening here. Let's check it out.

Expand Down Expand Up @@ -166,7 +166,7 @@ This will tell **FastAPI** to take the object that we return from the *path oper

In the case of the hero, this tells FastAPI to extract the `team` too. And in the case of the team, to extract the list of `heroes` too.

{* ./docs_src/tutorial/fastapi/relationships/tutorial001_py310.py ln[111:116,164:169] hl[111,116,164,169] *}
{* ./docs_src/tutorial/fastapi/relationships/tutorial001_py310.py ln[114:119,167:172] hl[114,119,167,172] *}

## Check It Out in the Docs UI

Expand Down
4 changes: 2 additions & 2 deletions docs/tutorial/fastapi/response-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@ We can use `response_model` to tell FastAPI the schema of the data we want to se

For example, we can pass the same `Hero` **SQLModel** class (because it is also a Pydantic model):

{* ./docs_src/tutorial/fastapi/response_model/tutorial001_py310.py ln[31:37] hl[31] *}
{* ./docs_src/tutorial/fastapi/response_model/tutorial001_py310.py ln[34:40] hl[34] *}

## List of Heroes in `response_model`

We can also use other type annotations, the same way we can use with Pydantic fields. For example, we can pass a list of `Hero`s.

To do so, we declare the `response_model` with `list[Hero]`:

{* ./docs_src/tutorial/fastapi/response_model/tutorial001_py310.py ln[40:44] hl[40] *}
{* ./docs_src/tutorial/fastapi/response_model/tutorial001_py310.py ln[43:47] hl[43] *}

## FastAPI and Response Model

Expand Down
12 changes: 6 additions & 6 deletions docs/tutorial/fastapi/session-with-dependency.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Before we keep adding things, let's change a bit how we get the session for each

Up to now, we have been creating a session in each *path operation*, in a `with` block.

{* ./docs_src/tutorial/fastapi/delete/tutorial001_py310.py ln[48:55] hl[50] *}
{* ./docs_src/tutorial/fastapi/delete/tutorial001_py310.py ln[51:58] hl[53] *}

That's perfectly fine, but in many use cases we would want to use <a href="https://fastapi.tiangolo.com/tutorial/dependencies/" class="external-link" target="_blank">FastAPI Dependencies</a>, for example to **verify** that the client is **logged in** and get the **current user** before executing any other code in the *path operation*.

Expand All @@ -20,15 +20,15 @@ A **FastAPI** dependency is very simple, it's just a function that returns a val

It could use `yield` instead of `return`, and in that case **FastAPI** will make sure it executes all the code **after** the `yield`, once it is done with the request.

{* ./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py310.py ln[40:42] hl[40:42] *}
{* ./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py310.py ln[42:44] hl[42:44] *}

## Use the Dependency

Now let's make FastAPI execute a dependency and get its value in the *path operation*.

We import `Depends()` from `fastapi`. Then we use it in the *path operation function* in a **parameter**, the same way we declared parameters to get JSON bodies, path parameters, etc.

{* ./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py310.py ln[1:2,40:42,53:59] hl[1,54] *}
{* ./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py310.py ln[3:4,42:44,56:61] hl[3,57] *}

/// tip

Expand Down Expand Up @@ -56,13 +56,13 @@ And because dependencies can use `yield`, FastAPI will make sure to run the code

This means that in the main code of the *path operation function*, it will work equivalently to the previous version with the explicit `with` block.

{* ./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py310.py ln[1:2,40:42,53:59] hl[55:59] *}
{* ./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py310.py ln[3:4,42:44,56:62] hl[57:62] *}

In fact, you could think that all that block of code inside of the `create_hero()` function is still inside a `with` block for the **session**, because this is more or less what's happening behind the scenes.

But now, the `with` block is not explicitly in the function, but in the dependency above:

{* ./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py310.py ln[1:2,40:42,53:59] hl[41:42] *}
{* ./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py310.py ln[3:4,42:44,56:62] hl[43:44] *}

We will see how this is very useful when testing the code later. ✅

Expand All @@ -78,7 +78,7 @@ session: Session = Depends(get_session)

And then we remove the previous `with` block with the old **session**.

{* ./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py310.py ln[1:2,40:42,53:104] hl[54,65,74,83,98] *}
{* ./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py310.py ln[3:5,42:44,56:107] hl[57,68,77,86,101] *}

## Recap

Expand Down
14 changes: 7 additions & 7 deletions docs/tutorial/fastapi/simple-hero-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ We will start with the **simplest version**, with just heroes (no teams yet).

This is almost the same code we have seen up to now in previous examples:

{* ./docs_src/tutorial/fastapi/simple_hero_api/tutorial001_py310.py ln[2,5:20] hl[19:20] *}
{* ./docs_src/tutorial/fastapi/simple_hero_api/tutorial001_py310.py ln[4,7:22] hl[21:22] *}

There's only one change here from the code we have used before, the `check_same_thread` in the `connect_args`.

Expand All @@ -54,17 +54,17 @@ The next step is to create the **FastAPI** app.

We will import the `FastAPI` class from `fastapi`.

And then create an `app` object that is an instance of that `FastAPI` class:
And then create an `app` object that is an instance of that `FastAPI` class, using a `lifespan` configuration that we will see next:

{* ./docs_src/tutorial/fastapi/simple_hero_api/tutorial001_py310.py ln[1:2,23] hl[1,23] *}
{* ./docs_src/tutorial/fastapi/simple_hero_api/tutorial001_py310.py ln[3:4,31] hl[3,31] *}

## Create Database and Tables on `startup`

We want to make sure that once the app starts running, the function `create_db_and_tables` is called. To create the database and tables.

This should be called only once at startup, not before every request, so we put it in the function to handle the `"startup"` event:
This should be called only once at startup, not before every request, so we use FastAPI's `lifespan` functionality:

{* ./docs_src/tutorial/fastapi/simple_hero_api/tutorial001_py310.py ln[23:28] hl[26:28] *}
{* ./docs_src/tutorial/fastapi/simple_hero_api/tutorial001_py310.py ln[21:31] hl[25:28] *}

## Create Heroes *Path Operation*

Expand All @@ -78,7 +78,7 @@ Let's create the **path operation** code to create a new hero.

It will be called when a user sends a request with a `POST` **operation** to the `/heroes/` **path**:

{* ./docs_src/tutorial/fastapi/simple_hero_api/tutorial001_py310.py ln[23:37] hl[31:32] *}
{* ./docs_src/tutorial/fastapi/simple_hero_api/tutorial001_py310.py ln[25:40] hl[34:35] *}

/// info

Expand Down Expand Up @@ -112,7 +112,7 @@ We will improve this further later, but for now, it already shows the power of h

Now let's add another **path operation** to read all the heroes:

{* ./docs_src/tutorial/fastapi/simple_hero_api/tutorial001_py310.py ln[23:44] hl[40:44] *}
{* ./docs_src/tutorial/fastapi/simple_hero_api/tutorial001_py310.py ln[21:47] hl[43:47] *}

This is pretty straightforward.

Expand Down
10 changes: 5 additions & 5 deletions docs/tutorial/fastapi/update-extra-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ The `Hero` table model will now store a new field `hashed_password`.

And the data models for `HeroCreate` and `HeroUpdate` will also have a new field `password` that will contain the plain text password sent by clients.

{* ./docs_src/tutorial/fastapi/update/tutorial002_py310.py ln[5:28] hl[13,17,28] *}
{* ./docs_src/tutorial/fastapi/update/tutorial002_py310.py ln[7:30] hl[15,19,30] *}

When a client is creating a new hero, they will send the `password` in the request body.

Expand All @@ -50,7 +50,7 @@ The app will receive the data from the client using the `HeroCreate` model.

This contains the `password` field with the plain text password, and we cannot use that one. So we need to generate a hash from it.

{* ./docs_src/tutorial/fastapi/update/tutorial002_py310.py ln[42:44,55:57] hl[57] *}
{* ./docs_src/tutorial/fastapi/update/tutorial002_py310.py ln[44:46,58:60] hl[60] *}

## Create an Object with Extra Data

Expand Down Expand Up @@ -106,7 +106,7 @@ So now, `db_user_dict` has the updated `age` field with `32` instead of `None` a

Similar to how dictionaries have an `update` method, **SQLModel** models have a parameter `update` in `Hero.model_validate()` that takes a dictionary with extra data, or data that should take precedence:

{* ./docs_src/tutorial/fastapi/update/tutorial002_py310.py ln[55:64] hl[60] *}
{* ./docs_src/tutorial/fastapi/update/tutorial002_py310.py ln[58:67] hl[63] *}

Now, `db_hero` (which is a *table model* `Hero`) will extract its values from `hero` (which is a *data model* `HeroCreate`), and then it will **`update`** its values with the extra data from the dictionary `extra_data`.

Expand All @@ -120,7 +120,7 @@ Now let's say we want to **update a hero** that already exists in the database.

The same way as before, to avoid removing existing data, we will use `exclude_unset=True` when calling `hero.model_dump()`, to get a dictionary with only the data sent by the client.

{* ./docs_src/tutorial/fastapi/update/tutorial002_py310.py ln[83:89] hl[89] *}
{* ./docs_src/tutorial/fastapi/update/tutorial002_py310.py ln[86:92] hl[92] *}

Now, this `hero_data` dictionary could contain a `password`. We need to check it, and if it's there, we need to generate the `hashed_password`.

Expand All @@ -130,7 +130,7 @@ And then we can update the `db_hero` object using the method `db_hero.sqlmodel_u

It takes a model object or dictionary with the data to update the object and also an **additional `update` argument** with extra data.

{* ./docs_src/tutorial/fastapi/update/tutorial002_py310.py ln[83:99] hl[95] *}
{* ./docs_src/tutorial/fastapi/update/tutorial002_py310.py ln[86:102] hl[98] *}

/// tip

Expand Down
10 changes: 5 additions & 5 deletions docs/tutorial/fastapi/update.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Because each field is **actually different** (we just set a default value of `No

So, let's create this new `HeroUpdate` model:

{* ./docs_src/tutorial/fastapi/update/tutorial001_py310.py ln[5:26] hl[23:26] *}
{* ./docs_src/tutorial/fastapi/update/tutorial001_py310.py ln[7:28] hl[25:28] *}

This is almost the same as `HeroBase`, but all the fields are optional, so we can't simply inherit from `HeroBase`.

Expand All @@ -32,7 +32,7 @@ Now let's use this model in the *path operation* to update a hero.

We will use a `PATCH` HTTP operation. This is used to **partially update data**, which is what we are doing.

{* ./docs_src/tutorial/fastapi/update/tutorial001_py310.py ln[74:89] hl[74:75] *}
{* ./docs_src/tutorial/fastapi/update/tutorial001_py310.py ln[77:91] hl[77:78] *}

We also read the `hero_id` from the *path parameter* and the request body, a `HeroUpdate`.

Expand All @@ -42,7 +42,7 @@ We take a `hero_id` with the **ID** of the hero **we want to update**.

So, we need to read the hero from the database, with the **same logic** we used to **read a single hero**, checking if it exists, possibly raising an error for the client if it doesn't exist, etc.

{* ./docs_src/tutorial/fastapi/update/tutorial001_py310.py ln[74:89] hl[77:79] *}
{* ./docs_src/tutorial/fastapi/update/tutorial001_py310.py ln[77:91] hl[80:82] *}

### Get the New Data

Expand Down Expand Up @@ -94,7 +94,7 @@ Then the dictionary we would get in Python using `hero.model_dump(exclude_unset=

Then we use that to get the data that was actually sent by the client:

{* ./docs_src/tutorial/fastapi/update/tutorial001_py310.py ln[74:89] hl[80] *}
{* ./docs_src/tutorial/fastapi/update/tutorial001_py310.py ln[77:91] hl[83] *}

/// 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.
Expand All @@ -104,7 +104,7 @@ Before SQLModel 0.0.14, the method was called `hero.dict(exclude_unset=True)`, b

Now that we have a **dictionary with the data sent by the client**, we can use the method `db_hero.sqlmodel_update()` to update the object `db_hero`.

{* ./docs_src/tutorial/fastapi/update/tutorial001_py310.py ln[74:89] hl[81] *}
{* ./docs_src/tutorial/fastapi/update/tutorial001_py310.py ln[77:91] hl[84] *}

/// tip

Expand Down
Loading
Loading