From ddd89bc4b34b347e6e0a3cdda0ae2c87422b1a78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 9 Nov 2026 19:09:19 +0100 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=93=9D=20Use=20Annotated=20for=20Fast?= =?UTF-8?q?API=20parameters?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/tutorial/fastapi/delete.md | 2 +- docs/tutorial/fastapi/limit-and-offset.md | 6 ++-- docs/tutorial/fastapi/relationships.md | 8 ++--- .../fastapi/session-with-dependency.md | 28 ++++++------------ docs/tutorial/fastapi/teams.md | 8 ++--- docs/tutorial/fastapi/update-extra-data.md | 10 +++---- docs/tutorial/fastapi/update.md | 10 +++---- .../annotations/en/test_main_002.md | 2 +- .../annotations/en/test_main_005.md | 2 +- .../fastapi/app_testing/tutorial001/main.py | 15 +++++----- .../tutorial/fastapi/delete/tutorial001.py | 4 ++- .../fastapi/limit_and_offset/tutorial001.py | 4 ++- .../fastapi/relationships/tutorial001.py | 29 +++++++++---------- .../session_with_dependency/tutorial001.py | 15 +++++----- .../tutorial/fastapi/teams/tutorial001.py | 29 +++++++++---------- .../tutorial/fastapi/update/tutorial001.py | 4 ++- .../tutorial/fastapi/update/tutorial002.py | 4 ++- 17 files changed, 90 insertions(+), 90 deletions(-) diff --git a/docs/tutorial/fastapi/delete.md b/docs/tutorial/fastapi/delete.md index 9dd4b218ea..ca1e767f57 100644 --- a/docs/tutorial/fastapi/delete.md +++ b/docs/tutorial/fastapi/delete.md @@ -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.py ln[89:97] hl[89:97] *} +{* ./docs_src/tutorial/fastapi/delete/tutorial001.py ln[91:98] hl[91:98] *} After deleting it successfully, we just return a response of: diff --git a/docs/tutorial/fastapi/limit-and-offset.md b/docs/tutorial/fastapi/limit-and-offset.md index b81b5c2244..4837698889 100644 --- a/docs/tutorial/fastapi/limit-and-offset.md +++ b/docs/tutorial/fastapi/limit-and-offset.md @@ -22,13 +22,15 @@ 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.py ln[1:2,52:56] hl[1,53,55] *} +We import `Annotated` from `typing` and `Query` from `fastapi`: + +{* ./docs_src/tutorial/fastapi/limit_and_offset/tutorial001.py ln[1:4,54:58] hl[1,3,55,57] *} We want to allow clients to set different `offset` and `limit` values. But we don't want them to be able to set a `limit` of something like `9999`, that's over `9000`! 😱 -So, to prevent it, we add additional validation to the `limit` query parameter, declaring that it has to be **l**ess than or **e**qual to `100` with `le=100`. +So, to prevent it, we add `Query(le=100)` inside `Annotated`, declaring that the `limit` query parameter has to be **l**ess than or **e**qual to `100`. The `= 100` sets its default value. This way, a client can decide to take fewer heroes if they want, but not more. diff --git a/docs/tutorial/fastapi/relationships.md b/docs/tutorial/fastapi/relationships.md index 204be3189b..36b1647854 100644 --- a/docs/tutorial/fastapi/relationships.md +++ b/docs/tutorial/fastapi/relationships.md @@ -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.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.py ln[7:9,22:23,31:36,45:46] hl[7:9,22:23,31:36,45:46] *} Now, remember that [FastAPI uses the `response_model` to validate and **filter** the response data](https://fastapi.tiangolo.com/tutorial/response-model/)? 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.py ln[102:107,155:160] hl[102,107,155,160] *} +{* ./docs_src/tutorial/fastapi/teams/tutorial001.py ln[103:108,155:160] hl[103,108,155,160] *} ## Don't Include All the Data @@ -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.py ln[59:64] hl[59:60,63:64] *} +{* ./docs_src/tutorial/fastapi/relationships/tutorial001.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. @@ -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.py ln[111:116,164:169] hl[111,116,164,169] *} +{* ./docs_src/tutorial/fastapi/relationships/tutorial001.py ln[112:117,164:169] hl[112,117,164,169] *} ## Check It Out in the Docs UI diff --git a/docs/tutorial/fastapi/session-with-dependency.md b/docs/tutorial/fastapi/session-with-dependency.md index a13054866e..eb5f3081b8 100644 --- a/docs/tutorial/fastapi/session-with-dependency.md +++ b/docs/tutorial/fastapi/session-with-dependency.md @@ -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.py ln[48:55] hl[50] *} +{* ./docs_src/tutorial/fastapi/delete/tutorial001.py ln[50:57] hl[52] *} That's perfectly fine, but in many use cases we would want to use [FastAPI Dependencies](https://fastapi.tiangolo.com/tutorial/dependencies/), for example to **verify** that the client is **logged in** and get the **current user** before executing any other code in the *path operation*. @@ -20,27 +20,17 @@ 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.py ln[40:42] hl[40:42] *} +{* ./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.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. +We import `Annotated` from `typing` and `Depends` from `fastapi`. Then we declare the `session` parameter as `Annotated[Session, Depends(get_session)]`. -{* ./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py ln[1:2,40:42,53:59] hl[1,54] *} +The type of the parameter is `Session`, and `Depends(get_session)` tells FastAPI to use `get_session` to provide its value. -/// tip - -Here's a tip about that `*,` thing in the parameters. - -Here we are passing the parameter `session` that has a "default value" of `Depends(get_session)` before the parameter `hero`, that doesn't have any default value. - -Python would normally complain about that, but we can use the initial "parameter" `*,` to mark all the rest of the parameters as "keyword only", which solves the problem. - -You can read more about it in the FastAPI documentation [Path Parameters and Numeric Validations - Order the parameters as you need, tricks](https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#order-the-parameters-as-you-need-tricks) - -/// +{* ./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py ln[1:4,42:44,55:61] hl[1,3,56] *} The value of a dependency will **only be used for one request**, FastAPI will call it right before calling your code and will give you the value from that dependency. @@ -56,13 +46,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.py ln[1:2,40:42,53:59] hl[55:59] *} +{* ./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py ln[1:4,42:44,55:61] hl[57:61] *} 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.py ln[1:2,40:42,53:59] hl[41:42] *} +{* ./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py ln[1:4,42:44,55:61] hl[43:44] *} We will see how this is very useful when testing the code later. ✅ @@ -73,12 +63,12 @@ Now we can update the rest of the *path operations* to use the new dependency. We just declare the dependency in the parameters of the function, with: ```Python -session: Session = Depends(get_session) +session: Annotated[Session, Depends(get_session)] ``` And then we remove the previous `with` block with the old **session**. -{* ./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py ln[1:2,40:42,53:104] hl[54,65,74,83,98] *} +{* ./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py ln[1:4,42:44,55:104] hl[56,66,75,84,98] *} ## Recap diff --git a/docs/tutorial/fastapi/teams.md b/docs/tutorial/fastapi/teams.md index 944284b053..5091603d68 100644 --- a/docs/tutorial/fastapi/teams.md +++ b/docs/tutorial/fastapi/teams.md @@ -18,7 +18,7 @@ Then we also inherit from the `TeamBase` for the `TeamCreate` and `TeamPublic` * And we also create a `TeamUpdate` **data model**. -{* ./docs_src/tutorial/fastapi/teams/tutorial001.py ln[1:26] hl[5:7,10:13,16:17,20:21,24:26] *} +{* ./docs_src/tutorial/fastapi/teams/tutorial001.py ln[1:28] hl[7:9,12:15,18:19,22:23,26:28] *} We now also have **relationship attributes**. 🎉 @@ -26,7 +26,7 @@ Let's now update the `Hero` models too. ## Update Hero Models -{* ./docs_src/tutorial/fastapi/teams/tutorial001.py ln[29:55] hl[29:34,37:40,43:44,47:48,51:55] *} +{* ./docs_src/tutorial/fastapi/teams/tutorial001.py ln[31:57] hl[31:36,39:42,45:46,49:50,53:57] *} We now have a `team_id` in the hero models. @@ -38,7 +38,7 @@ And even though the `HeroBase` is *not* a **table model**, we can declare `team_ Notice that the **relationship attributes**, the ones with `Relationship()`, are **only** in the **table models**, as those are the ones that are handled by **SQLModel** with SQLAlchemy and that can have the automatic fetching of data from the database when we access them. -{* ./docs_src/tutorial/fastapi/teams/tutorial001.py ln[5:55] hl[13,40] *} +{* ./docs_src/tutorial/fastapi/teams/tutorial001.py ln[7:57] hl[15,42] *} ## Path Operations for Teams @@ -46,7 +46,7 @@ Let's now add the **path operations** for teams. These are equivalent and very similar to the **path operations** for the **heroes** we had before, so we don't have to go over the details for each one, let's check the code. -{* ./docs_src/tutorial/fastapi/teams/tutorial001.py ln[135:188] hl[135:141,144:152,155:160,163:178,181:188] *} +{* ./docs_src/tutorial/fastapi/teams/tutorial001.py ln[136:187] hl[136:142,145:152,155:160,163:177,180:187] *} ## Using Relationship Attributes diff --git a/docs/tutorial/fastapi/update-extra-data.md b/docs/tutorial/fastapi/update-extra-data.md index 6c97a975b1..f1ce3cc440 100644 --- a/docs/tutorial/fastapi/update-extra-data.md +++ b/docs/tutorial/fastapi/update-extra-data.md @@ -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.py ln[5:28] hl[13,17,28] *} +{* ./docs_src/tutorial/fastapi/update/tutorial002.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. @@ -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.py ln[42:44,55:57] hl[57] *} +{* ./docs_src/tutorial/fastapi/update/tutorial002.py ln[44:46,57:59] hl[59] *} ## Create an Object with Extra Data @@ -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.py ln[55:64] hl[60] *} +{* ./docs_src/tutorial/fastapi/update/tutorial002.py ln[57:66] hl[62] *} 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`. @@ -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.py ln[83:89] hl[89] *} +{* ./docs_src/tutorial/fastapi/update/tutorial002.py ln[85:91] hl[91] *} 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`. @@ -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.py ln[83:99] hl[95] *} +{* ./docs_src/tutorial/fastapi/update/tutorial002.py ln[85:101] hl[97] *} /// tip diff --git a/docs/tutorial/fastapi/update.md b/docs/tutorial/fastapi/update.md index 58fa9faa74..9500d527ce 100644 --- a/docs/tutorial/fastapi/update.md +++ b/docs/tutorial/fastapi/update.md @@ -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.py ln[5:26] hl[23:26] *} +{* ./docs_src/tutorial/fastapi/update/tutorial001.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`. @@ -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.py ln[74:89] hl[74:75] *} +{* ./docs_src/tutorial/fastapi/update/tutorial001.py ln[76:87] hl[76:77] *} We also read the `hero_id` from the *path parameter* and the request body, a `HeroUpdate`. @@ -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.py ln[74:89] hl[77:79] *} +{* ./docs_src/tutorial/fastapi/update/tutorial001.py ln[76:87] hl[79:81] *} ### Get the New Data @@ -92,7 +92,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.py ln[74:89] hl[80] *} +{* ./docs_src/tutorial/fastapi/update/tutorial001.py ln[76:87] hl[82] *} /// 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. @@ -102,7 +102,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.py ln[74:89] hl[81] *} +{* ./docs_src/tutorial/fastapi/update/tutorial001.py ln[76:87] hl[83] *} /// tip diff --git a/docs_src/tutorial/fastapi/app_testing/tutorial001/annotations/en/test_main_002.md b/docs_src/tutorial/fastapi/app_testing/tutorial001/annotations/en/test_main_002.md index 0f8555a8dd..2597ac4d6f 100644 --- a/docs_src/tutorial/fastapi/app_testing/tutorial001/annotations/en/test_main_002.md +++ b/docs_src/tutorial/fastapi/app_testing/tutorial001/annotations/en/test_main_002.md @@ -17,7 +17,7 @@ So, here we are telling the FastAPI app to use `get_session_override` instead of `get_session` in all the places in the code that depend on `get_session`, that is, all the parameters with something like: ```Python - session: Session = Depends(get_session) + session: Annotated[Session, Depends(get_session)] ``` 5. After we are done with the dependency override, we can restore the application back to normal, by removing all the values in this dictionary `app.dependency_overrides`. diff --git a/docs_src/tutorial/fastapi/app_testing/tutorial001/annotations/en/test_main_005.md b/docs_src/tutorial/fastapi/app_testing/tutorial001/annotations/en/test_main_005.md index e361871455..996d00c1ef 100644 --- a/docs_src/tutorial/fastapi/app_testing/tutorial001/annotations/en/test_main_005.md +++ b/docs_src/tutorial/fastapi/app_testing/tutorial001/annotations/en/test_main_005.md @@ -27,7 +27,7 @@ 5. Now, in the test function, to tell **pytest** that this test wants to get the fixture, instead of declaring something like in FastAPI with: ```Python - session: Session = Depends(session_fixture) + session: Annotated[Session, Depends(session_fixture)] ``` ...the way we tell pytest what is the fixture that we want is by using the **exact same name** of the fixture. diff --git a/docs_src/tutorial/fastapi/app_testing/tutorial001/main.py b/docs_src/tutorial/fastapi/app_testing/tutorial001/main.py index 84da9fd610..6f205971b1 100644 --- a/docs_src/tutorial/fastapi/app_testing/tutorial001/main.py +++ b/docs_src/tutorial/fastapi/app_testing/tutorial001/main.py @@ -1,3 +1,5 @@ +from typing import Annotated + from fastapi import Depends, FastAPI, HTTPException, Query from sqlmodel import Field, Session, SQLModel, create_engine, select @@ -51,7 +53,7 @@ def on_startup(): @app.post("/heroes/", response_model=HeroPublic) -def create_hero(*, session: Session = Depends(get_session), hero: HeroCreate): +def create_hero(session: Annotated[Session, Depends(get_session)], hero: HeroCreate): db_hero = Hero.model_validate(hero) session.add(db_hero) session.commit() @@ -61,17 +63,16 @@ def create_hero(*, session: Session = Depends(get_session), hero: HeroCreate): @app.get("/heroes/", response_model=list[HeroPublic]) def read_heroes( - *, - session: Session = Depends(get_session), + session: Annotated[Session, Depends(get_session)], offset: int = 0, - limit: int = Query(default=100, le=100), + limit: Annotated[int, Query(le=100)] = 100, ): heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() return heroes @app.get("/heroes/{hero_id}", response_model=HeroPublic) -def read_hero(*, session: Session = Depends(get_session), hero_id: int): +def read_hero(session: Annotated[Session, Depends(get_session)], hero_id: int): hero = session.get(Hero, hero_id) if not hero: raise HTTPException(status_code=404, detail="Hero not found") @@ -80,7 +81,7 @@ def read_hero(*, session: Session = Depends(get_session), hero_id: int): @app.patch("/heroes/{hero_id}", response_model=HeroPublic) def update_hero( - *, session: Session = Depends(get_session), hero_id: int, hero: HeroUpdate + session: Annotated[Session, Depends(get_session)], hero_id: int, hero: HeroUpdate ): db_hero = session.get(Hero, hero_id) if not db_hero: @@ -94,7 +95,7 @@ def update_hero( @app.delete("/heroes/{hero_id}") -def delete_hero(*, session: Session = Depends(get_session), hero_id: int): +def delete_hero(session: Annotated[Session, Depends(get_session)], hero_id: int): hero = session.get(Hero, hero_id) if not hero: raise HTTPException(status_code=404, detail="Hero not found") diff --git a/docs_src/tutorial/fastapi/delete/tutorial001.py b/docs_src/tutorial/fastapi/delete/tutorial001.py index f7de4019d2..5a817275f0 100644 --- a/docs_src/tutorial/fastapi/delete/tutorial001.py +++ b/docs_src/tutorial/fastapi/delete/tutorial001.py @@ -1,3 +1,5 @@ +from typing import Annotated + from fastapi import FastAPI, HTTPException, Query from sqlmodel import Field, Session, SQLModel, create_engine, select @@ -56,7 +58,7 @@ def create_hero(hero: HeroCreate): @app.get("/heroes/", response_model=list[HeroPublic]) -def read_heroes(offset: int = 0, limit: int = Query(default=100, le=100)): +def read_heroes(offset: int = 0, limit: Annotated[int, Query(le=100)] = 100): with Session(engine) as session: heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() return heroes diff --git a/docs_src/tutorial/fastapi/limit_and_offset/tutorial001.py b/docs_src/tutorial/fastapi/limit_and_offset/tutorial001.py index 3402d4045e..1f1a9fe1ac 100644 --- a/docs_src/tutorial/fastapi/limit_and_offset/tutorial001.py +++ b/docs_src/tutorial/fastapi/limit_and_offset/tutorial001.py @@ -1,3 +1,5 @@ +from typing import Annotated + from fastapi import FastAPI, HTTPException, Query from sqlmodel import Field, Session, SQLModel, create_engine, select @@ -50,7 +52,7 @@ def create_hero(hero: HeroCreate): @app.get("/heroes/", response_model=list[HeroPublic]) -def read_heroes(offset: int = 0, limit: int = Query(default=100, le=100)): +def read_heroes(offset: int = 0, limit: Annotated[int, Query(le=100)] = 100): with Session(engine) as session: heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() return heroes diff --git a/docs_src/tutorial/fastapi/relationships/tutorial001.py b/docs_src/tutorial/fastapi/relationships/tutorial001.py index 47c2e5f0bc..2d623cf946 100644 --- a/docs_src/tutorial/fastapi/relationships/tutorial001.py +++ b/docs_src/tutorial/fastapi/relationships/tutorial001.py @@ -1,3 +1,5 @@ +from typing import Annotated + from fastapi import Depends, FastAPI, HTTPException, Query from sqlmodel import Field, Relationship, Session, SQLModel, create_engine, select @@ -89,7 +91,7 @@ def on_startup(): @app.post("/heroes/", response_model=HeroPublic) -def create_hero(*, session: Session = Depends(get_session), hero: HeroCreate): +def create_hero(session: Annotated[Session, Depends(get_session)], hero: HeroCreate): db_hero = Hero.model_validate(hero) session.add(db_hero) session.commit() @@ -99,17 +101,16 @@ def create_hero(*, session: Session = Depends(get_session), hero: HeroCreate): @app.get("/heroes/", response_model=list[HeroPublic]) def read_heroes( - *, - session: Session = Depends(get_session), + session: Annotated[Session, Depends(get_session)], offset: int = 0, - limit: int = Query(default=100, le=100), + limit: Annotated[int, Query(le=100)] = 100, ): heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() return heroes @app.get("/heroes/{hero_id}", response_model=HeroPublicWithTeam) -def read_hero(*, session: Session = Depends(get_session), hero_id: int): +def read_hero(session: Annotated[Session, Depends(get_session)], hero_id: int): hero = session.get(Hero, hero_id) if not hero: raise HTTPException(status_code=404, detail="Hero not found") @@ -118,7 +119,7 @@ def read_hero(*, session: Session = Depends(get_session), hero_id: int): @app.patch("/heroes/{hero_id}", response_model=HeroPublic) def update_hero( - *, session: Session = Depends(get_session), hero_id: int, hero: HeroUpdate + session: Annotated[Session, Depends(get_session)], hero_id: int, hero: HeroUpdate ): db_hero = session.get(Hero, hero_id) if not db_hero: @@ -132,7 +133,7 @@ def update_hero( @app.delete("/heroes/{hero_id}") -def delete_hero(*, session: Session = Depends(get_session), hero_id: int): +def delete_hero(session: Annotated[Session, Depends(get_session)], hero_id: int): hero = session.get(Hero, hero_id) if not hero: raise HTTPException(status_code=404, detail="Hero not found") @@ -142,7 +143,7 @@ def delete_hero(*, session: Session = Depends(get_session), hero_id: int): @app.post("/teams/", response_model=TeamPublic) -def create_team(*, session: Session = Depends(get_session), team: TeamCreate): +def create_team(session: Annotated[Session, Depends(get_session)], team: TeamCreate): db_team = Team.model_validate(team) session.add(db_team) session.commit() @@ -152,17 +153,16 @@ def create_team(*, session: Session = Depends(get_session), team: TeamCreate): @app.get("/teams/", response_model=list[TeamPublic]) def read_teams( - *, - session: Session = Depends(get_session), + session: Annotated[Session, Depends(get_session)], offset: int = 0, - limit: int = Query(default=100, le=100), + limit: Annotated[int, Query(le=100)] = 100, ): teams = session.exec(select(Team).offset(offset).limit(limit)).all() return teams @app.get("/teams/{team_id}", response_model=TeamPublicWithHeroes) -def read_team(*, team_id: int, session: Session = Depends(get_session)): +def read_team(team_id: int, session: Annotated[Session, Depends(get_session)]): team = session.get(Team, team_id) if not team: raise HTTPException(status_code=404, detail="Team not found") @@ -171,8 +171,7 @@ def read_team(*, team_id: int, session: Session = Depends(get_session)): @app.patch("/teams/{team_id}", response_model=TeamPublic) def update_team( - *, - session: Session = Depends(get_session), + session: Annotated[Session, Depends(get_session)], team_id: int, team: TeamUpdate, ): @@ -188,7 +187,7 @@ def update_team( @app.delete("/teams/{team_id}") -def delete_team(*, session: Session = Depends(get_session), team_id: int): +def delete_team(session: Annotated[Session, Depends(get_session)], team_id: int): team = session.get(Team, team_id) if not team: raise HTTPException(status_code=404, detail="Team not found") diff --git a/docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py b/docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py index 84da9fd610..6f205971b1 100644 --- a/docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py +++ b/docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py @@ -1,3 +1,5 @@ +from typing import Annotated + from fastapi import Depends, FastAPI, HTTPException, Query from sqlmodel import Field, Session, SQLModel, create_engine, select @@ -51,7 +53,7 @@ def on_startup(): @app.post("/heroes/", response_model=HeroPublic) -def create_hero(*, session: Session = Depends(get_session), hero: HeroCreate): +def create_hero(session: Annotated[Session, Depends(get_session)], hero: HeroCreate): db_hero = Hero.model_validate(hero) session.add(db_hero) session.commit() @@ -61,17 +63,16 @@ def create_hero(*, session: Session = Depends(get_session), hero: HeroCreate): @app.get("/heroes/", response_model=list[HeroPublic]) def read_heroes( - *, - session: Session = Depends(get_session), + session: Annotated[Session, Depends(get_session)], offset: int = 0, - limit: int = Query(default=100, le=100), + limit: Annotated[int, Query(le=100)] = 100, ): heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() return heroes @app.get("/heroes/{hero_id}", response_model=HeroPublic) -def read_hero(*, session: Session = Depends(get_session), hero_id: int): +def read_hero(session: Annotated[Session, Depends(get_session)], hero_id: int): hero = session.get(Hero, hero_id) if not hero: raise HTTPException(status_code=404, detail="Hero not found") @@ -80,7 +81,7 @@ def read_hero(*, session: Session = Depends(get_session), hero_id: int): @app.patch("/heroes/{hero_id}", response_model=HeroPublic) def update_hero( - *, session: Session = Depends(get_session), hero_id: int, hero: HeroUpdate + session: Annotated[Session, Depends(get_session)], hero_id: int, hero: HeroUpdate ): db_hero = session.get(Hero, hero_id) if not db_hero: @@ -94,7 +95,7 @@ def update_hero( @app.delete("/heroes/{hero_id}") -def delete_hero(*, session: Session = Depends(get_session), hero_id: int): +def delete_hero(session: Annotated[Session, Depends(get_session)], hero_id: int): hero = session.get(Hero, hero_id) if not hero: raise HTTPException(status_code=404, detail="Hero not found") diff --git a/docs_src/tutorial/fastapi/teams/tutorial001.py b/docs_src/tutorial/fastapi/teams/tutorial001.py index b78f059e12..5597fedf8b 100644 --- a/docs_src/tutorial/fastapi/teams/tutorial001.py +++ b/docs_src/tutorial/fastapi/teams/tutorial001.py @@ -1,3 +1,5 @@ +from typing import Annotated + from fastapi import Depends, FastAPI, HTTPException, Query from sqlmodel import Field, Relationship, Session, SQLModel, create_engine, select @@ -80,7 +82,7 @@ def on_startup(): @app.post("/heroes/", response_model=HeroPublic) -def create_hero(*, session: Session = Depends(get_session), hero: HeroCreate): +def create_hero(session: Annotated[Session, Depends(get_session)], hero: HeroCreate): db_hero = Hero.model_validate(hero) session.add(db_hero) session.commit() @@ -90,17 +92,16 @@ def create_hero(*, session: Session = Depends(get_session), hero: HeroCreate): @app.get("/heroes/", response_model=list[HeroPublic]) def read_heroes( - *, - session: Session = Depends(get_session), + session: Annotated[Session, Depends(get_session)], offset: int = 0, - limit: int = Query(default=100, le=100), + limit: Annotated[int, Query(le=100)] = 100, ): heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() return heroes @app.get("/heroes/{hero_id}", response_model=HeroPublic) -def read_hero(*, session: Session = Depends(get_session), hero_id: int): +def read_hero(session: Annotated[Session, Depends(get_session)], hero_id: int): hero = session.get(Hero, hero_id) if not hero: raise HTTPException(status_code=404, detail="Hero not found") @@ -109,7 +110,7 @@ def read_hero(*, session: Session = Depends(get_session), hero_id: int): @app.patch("/heroes/{hero_id}", response_model=HeroPublic) def update_hero( - *, session: Session = Depends(get_session), hero_id: int, hero: HeroUpdate + session: Annotated[Session, Depends(get_session)], hero_id: int, hero: HeroUpdate ): db_hero = session.get(Hero, hero_id) if not db_hero: @@ -123,7 +124,7 @@ def update_hero( @app.delete("/heroes/{hero_id}") -def delete_hero(*, session: Session = Depends(get_session), hero_id: int): +def delete_hero(session: Annotated[Session, Depends(get_session)], hero_id: int): hero = session.get(Hero, hero_id) if not hero: raise HTTPException(status_code=404, detail="Hero not found") @@ -133,7 +134,7 @@ def delete_hero(*, session: Session = Depends(get_session), hero_id: int): @app.post("/teams/", response_model=TeamPublic) -def create_team(*, session: Session = Depends(get_session), team: TeamCreate): +def create_team(session: Annotated[Session, Depends(get_session)], team: TeamCreate): db_team = Team.model_validate(team) session.add(db_team) session.commit() @@ -143,17 +144,16 @@ def create_team(*, session: Session = Depends(get_session), team: TeamCreate): @app.get("/teams/", response_model=list[TeamPublic]) def read_teams( - *, - session: Session = Depends(get_session), + session: Annotated[Session, Depends(get_session)], offset: int = 0, - limit: int = Query(default=100, le=100), + limit: Annotated[int, Query(le=100)] = 100, ): teams = session.exec(select(Team).offset(offset).limit(limit)).all() return teams @app.get("/teams/{team_id}", response_model=TeamPublic) -def read_team(*, team_id: int, session: Session = Depends(get_session)): +def read_team(team_id: int, session: Annotated[Session, Depends(get_session)]): team = session.get(Team, team_id) if not team: raise HTTPException(status_code=404, detail="Team not found") @@ -162,8 +162,7 @@ def read_team(*, team_id: int, session: Session = Depends(get_session)): @app.patch("/teams/{team_id}", response_model=TeamPublic) def update_team( - *, - session: Session = Depends(get_session), + session: Annotated[Session, Depends(get_session)], team_id: int, team: TeamUpdate, ): @@ -179,7 +178,7 @@ def update_team( @app.delete("/teams/{team_id}") -def delete_team(*, session: Session = Depends(get_session), team_id: int): +def delete_team(session: Annotated[Session, Depends(get_session)], team_id: int): team = session.get(Team, team_id) if not team: raise HTTPException(status_code=404, detail="Team not found") diff --git a/docs_src/tutorial/fastapi/update/tutorial001.py b/docs_src/tutorial/fastapi/update/tutorial001.py index a98ee68fbb..159d41a1cf 100644 --- a/docs_src/tutorial/fastapi/update/tutorial001.py +++ b/docs_src/tutorial/fastapi/update/tutorial001.py @@ -1,3 +1,5 @@ +from typing import Annotated + from fastapi import FastAPI, HTTPException, Query from sqlmodel import Field, Session, SQLModel, create_engine, select @@ -56,7 +58,7 @@ def create_hero(hero: HeroCreate): @app.get("/heroes/", response_model=list[HeroPublic]) -def read_heroes(offset: int = 0, limit: int = Query(default=100, le=100)): +def read_heroes(offset: int = 0, limit: Annotated[int, Query(le=100)] = 100): with Session(engine) as session: heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() return heroes diff --git a/docs_src/tutorial/fastapi/update/tutorial002.py b/docs_src/tutorial/fastapi/update/tutorial002.py index d250fecf3a..8d186fcd9a 100644 --- a/docs_src/tutorial/fastapi/update/tutorial002.py +++ b/docs_src/tutorial/fastapi/update/tutorial002.py @@ -1,3 +1,5 @@ +from typing import Annotated + from fastapi import FastAPI, HTTPException, Query from sqlmodel import Field, Session, SQLModel, create_engine, select @@ -65,7 +67,7 @@ def create_hero(hero: HeroCreate): @app.get("/heroes/", response_model=list[HeroPublic]) -def read_heroes(offset: int = 0, limit: int = Query(default=100, le=100)): +def read_heroes(offset: int = 0, limit: Annotated[int, Query(le=100)] = 100): with Session(engine) as session: heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() return heroes From bab3eedaef9fd8d3a906e748cd416a1a64e59175 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 9 Nov 2026 19:37:02 +0100 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=91=B7=20Retry=20CI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit