From 9ec452a154e15e4af0f57f51a4fe1fee58879c4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 15 Feb 2025 17:23:59 +0100 Subject: [PATCH 001/352] =?UTF-8?q?=F0=9F=93=9D=20Update=20docs=20for=20Qu?= =?UTF-8?q?ery=20Params=20and=20String=20Validations,=20remove=20obsolete?= =?UTF-8?q?=20Ellipsis=20docs=20(`...`)=20(#13377)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tutorial/query-params-str-validations.md | 16 --- .../tutorial/query-params-str-validations.md | 28 ----- .../tutorial/query-params-str-validations.md | 117 +++--------------- .../tutorial/query-params-str-validations.md | 16 --- .../tutorial/query-params-str-validations.md | 28 ----- .../tutorial/query-params-str-validations.md | 27 ---- .../tutorial006b.py | 11 -- .../tutorial006b_an.py | 12 -- .../tutorial006b_an_py39.py | 13 -- .../tutorial006c.py | 2 +- .../tutorial006c_an.py | 2 +- .../tutorial006c_an_py310.py | 2 +- .../tutorial006c_an_py39.py | 2 +- .../tutorial006c_py310.py | 2 +- .../tutorial006d.py | 11 -- .../tutorial006d_an.py | 12 -- .../tutorial006d_an_py39.py | 13 -- 17 files changed, 20 insertions(+), 294 deletions(-) delete mode 100644 docs_src/query_params_str_validations/tutorial006b.py delete mode 100644 docs_src/query_params_str_validations/tutorial006b_an.py delete mode 100644 docs_src/query_params_str_validations/tutorial006b_an_py39.py delete mode 100644 docs_src/query_params_str_validations/tutorial006d.py delete mode 100644 docs_src/query_params_str_validations/tutorial006d_an.py delete mode 100644 docs_src/query_params_str_validations/tutorial006d_an_py39.py diff --git a/docs/de/docs/tutorial/query-params-str-validations.md b/docs/de/docs/tutorial/query-params-str-validations.md index f181d501c..de8879ce8 100644 --- a/docs/de/docs/tutorial/query-params-str-validations.md +++ b/docs/de/docs/tutorial/query-params-str-validations.md @@ -315,22 +315,6 @@ Wenn Sie einen Parameter erforderlich machen wollen, während Sie `Query` verwen {* ../../docs_src/query_params_str_validations/tutorial006_an_py39.py hl[9] *} -### Erforderlich mit Ellipse (`...`) - -Es gibt eine Alternative, die explizit deklariert, dass ein Wert erforderlich ist. Sie können als Default das Literal `...` setzen: - -{* ../../docs_src/query_params_str_validations/tutorial006b_an_py39.py hl[9] *} - -/// info - -Falls Sie das `...` bisher noch nicht gesehen haben: Es ist ein spezieller einzelner Wert, Teil von Python und wird „Ellipsis“ genannt (Deutsch: Ellipse). - -Es wird von Pydantic und FastAPI verwendet, um explizit zu deklarieren, dass ein Wert erforderlich ist. - -/// - -Dies wird **FastAPI** wissen lassen, dass dieser Parameter erforderlich ist. - ### Erforderlich, kann `None` sein Sie können deklarieren, dass ein Parameter `None` akzeptiert, aber dennoch erforderlich ist. Das zwingt Clients, den Wert zu senden, selbst wenn er `None` ist. diff --git a/docs/em/docs/tutorial/query-params-str-validations.md b/docs/em/docs/tutorial/query-params-str-validations.md index dbaab5735..fd077bf8f 100644 --- a/docs/em/docs/tutorial/query-params-str-validations.md +++ b/docs/em/docs/tutorial/query-params-str-validations.md @@ -148,22 +148,6 @@ q: Union[str, None] = Query(default=None, min_length=3) {* ../../docs_src/query_params_str_validations/tutorial006.py hl[7] *} -### ✔ ⏮️ ❕ (`...`) - -📤 🎛 🌌 🎯 📣 👈 💲 ✔. 👆 💪 ⚒ `default` 🔢 🔑 💲 `...`: - -{* ../../docs_src/query_params_str_validations/tutorial006b.py hl[7] *} - -/// info - -🚥 👆 🚫 👀 👈 `...` ⏭: ⚫️ 🎁 👁 💲, ⚫️ 🍕 🐍 & 🤙 "❕". - -⚫️ ⚙️ Pydantic & FastAPI 🎯 📣 👈 💲 ✔. - -/// - -👉 🔜 ➡️ **FastAPI** 💭 👈 👉 🔢 ✔. - ### ✔ ⏮️ `None` 👆 💪 📣 👈 🔢 💪 🚫 `None`, ✋️ 👈 ⚫️ ✔. 👉 🔜 ⚡ 👩‍💻 📨 💲, 🚥 💲 `None`. @@ -178,18 +162,6 @@ Pydantic, ❔ ⚫️❔ 🏋️ 🌐 💽 🔬 & 🛠️ FastAPI, ✔️ /// -### ⚙️ Pydantic `Required` ↩️ ❕ (`...`) - -🚥 👆 💭 😬 ⚙️ `...`, 👆 💪 🗄 & ⚙️ `Required` ⚪️➡️ Pydantic: - -{* ../../docs_src/query_params_str_validations/tutorial006d.py hl[2,8] *} - -/// tip - -💭 👈 🌅 💼, 🕐❔ 🕳 🚚, 👆 💪 🎯 🚫 `default` 🔢, 👆 🛎 🚫 ✔️ ⚙️ `...` 🚫 `Required`. - -/// - ## 🔢 🔢 📇 / 💗 💲 🕐❔ 👆 🔬 🔢 🔢 🎯 ⏮️ `Query` 👆 💪 📣 ⚫️ 📨 📇 💲, ⚖️ 🙆‍♀ 🎏 🌌, 📨 💗 💲. diff --git a/docs/en/docs/tutorial/query-params-str-validations.md b/docs/en/docs/tutorial/query-params-str-validations.md index 1bf16334d..511099186 100644 --- a/docs/en/docs/tutorial/query-params-str-validations.md +++ b/docs/en/docs/tutorial/query-params-str-validations.md @@ -6,13 +6,13 @@ Let's take this application as example: {* ../../docs_src/query_params_str_validations/tutorial001_py310.py hl[7] *} -The query parameter `q` is of type `Union[str, None]` (or `str | None` in Python 3.10), that means that it's of type `str` but could also be `None`, and indeed, the default value is `None`, so FastAPI will know it's not required. +The query parameter `q` is of type `str | None`, that means that it's of type `str` but could also be `None`, and indeed, the default value is `None`, so FastAPI will know it's not required. /// note FastAPI will know that the value of `q` is not required because of the default value `= None`. -The `Union` in `Union[str, None]` will allow your editor to give you better support and detect errors. +Having `str | None` will allow your editor to give you better support and detect errors. /// @@ -25,29 +25,9 @@ We are going to enforce that even though `q` is optional, whenever it is provide To achieve that, first import: * `Query` from `fastapi` -* `Annotated` from `typing` (or from `typing_extensions` in Python below 3.9) +* `Annotated` from `typing` -//// tab | Python 3.10+ - -In Python 3.9 or above, `Annotated` is part of the standard library, so you can import it from `typing`. - -```Python hl_lines="1 3" -{!> ../../docs_src/query_params_str_validations/tutorial002_an_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -In versions of Python below Python 3.9 you import `Annotated` from `typing_extensions`. - -It will already be installed with FastAPI. - -```Python hl_lines="3-4" -{!> ../../docs_src/query_params_str_validations/tutorial002_an.py!} -``` - -//// +{* ../../docs_src/query_params_str_validations/tutorial002_an_py310.py hl[1,3] *} /// info @@ -145,54 +125,23 @@ As in this case (without using `Annotated`) we have to replace the default value So: -```Python -q: Union[str, None] = Query(default=None) -``` - -...makes the parameter optional, with a default value of `None`, the same as: - -```Python -q: Union[str, None] = None -``` - -And in Python 3.10 and above: - ```Python q: str | None = Query(default=None) ``` ...makes the parameter optional, with a default value of `None`, the same as: -```Python -q: str | None = None -``` - -But the `Query` versions declare it explicitly as being a query parameter. - -/// info - -Keep in mind that the most important part to make a parameter optional is the part: ```Python -= None -``` - -or the: - -```Python -= Query(default=None) +q: str | None = None ``` -as it will use that `None` as the default value, and that way make the parameter **not required**. - -The `Union[str, None]` part allows your editor to provide better support, but it is not what tells FastAPI that this parameter is not required. - -/// +But the `Query` version declares it explicitly as being a query parameter. Then, we can pass more parameters to `Query`. In this case, the `max_length` parameter that applies to strings: ```Python -q: Union[str, None] = Query(default=None, max_length=50) +q: str | None = Query(default=None, max_length=50) ``` This will validate the data, show a clear error when the data is not valid, and document the parameter in the OpenAPI schema *path operation*. @@ -201,7 +150,7 @@ This will validate the data, show a clear error when the data is not valid, and Keep in mind that when using `Query` inside of `Annotated` you cannot use the `default` parameter for `Query`. -Instead use the actual default value of the function parameter. Otherwise, it would be inconsistent. +Instead, use the actual default value of the function parameter. Otherwise, it would be inconsistent. For example, this is not allowed: @@ -255,7 +204,7 @@ This specific regular expression pattern checks that the received parameter valu If you feel lost with all these **"regular expression"** ideas, don't worry. They are a hard topic for many people. You can still do a lot of stuff without needing regular expressions yet. -But whenever you need them and go and learn them, know that you can already use them directly in **FastAPI**. +Now you know that whenever you need them you can use them in **FastAPI**. ### Pydantic v1 `regex` instead of `pattern` @@ -296,7 +245,7 @@ q: str instead of: ```Python -q: Union[str, None] = None +q: str | None = None ``` But we are now declaring it with `Query`, for example like: @@ -304,15 +253,7 @@ But we are now declaring it with `Query`, for example like: //// tab | Annotated ```Python -q: Annotated[Union[str, None], Query(min_length=3)] = None -``` - -//// - -//// tab | non-Annotated - -```Python -q: Union[str, None] = Query(default=None, min_length=3) +q: Annotated[str | None, Query(min_length=3)] = None ``` //// @@ -321,42 +262,14 @@ So, when you need to declare a value as required while using `Query`, you can si {* ../../docs_src/query_params_str_validations/tutorial006_an_py39.py hl[9] *} -### Required with Ellipsis (`...`) - -There's an alternative way to explicitly declare that a value is required. You can set the default to the literal value `...`: - -{* ../../docs_src/query_params_str_validations/tutorial006b_an_py39.py hl[9] *} - -/// info - -If you hadn't seen that `...` before: it is a special single value, it is part of Python and is called "Ellipsis". - -It is used by Pydantic and FastAPI to explicitly declare that a value is required. - -/// - -This will let **FastAPI** know that this parameter is required. - ### Required, can be `None` You can declare that a parameter can accept `None`, but that it's still required. This would force clients to send a value, even if the value is `None`. -To do that, you can declare that `None` is a valid type but still use `...` as the default: +To do that, you can declare that `None` is a valid type but simply do not declare a default value: {* ../../docs_src/query_params_str_validations/tutorial006c_an_py310.py hl[9] *} -/// tip - -Pydantic, which is what powers all the data validation and serialization in FastAPI, has a special behavior when you use `Optional` or `Union[Something, None]` without a default value, you can read more about it in the Pydantic docs about Required fields. - -/// - -/// tip - -Remember that in most of the cases, when something is required, you can simply omit the default, so you normally don't have to use `...`. - -/// - ## Query parameter list / multiple values When you define a query parameter explicitly with `Query` you can also declare it to receive a list of values, or said in another way, to receive multiple values. @@ -396,7 +309,7 @@ The interactive API docs will update accordingly, to allow multiple values: ### Query parameter list / multiple values with defaults -And you can also define a default `list` of values if none are provided: +You can also define a default `list` of values if none are provided: {* ../../docs_src/query_params_str_validations/tutorial012_an_py39.py hl[9] *} @@ -419,7 +332,7 @@ the default of `q` will be: `["foo", "bar"]` and your response will be: #### Using just `list` -You can also use `list` directly instead of `List[str]` (or `list[str]` in Python 3.9+): +You can also use `list` directly instead of `list[str]`: {* ../../docs_src/query_params_str_validations/tutorial013_an_py39.py hl[9] *} @@ -427,7 +340,7 @@ You can also use `list` directly instead of `List[str]` (or `list[str]` in Pytho Keep in mind that in this case, FastAPI won't check the contents of the list. -For example, `List[int]` would check (and document) that the contents of the list are integers. But `list` alone wouldn't. +For example, `list[int]` would check (and document) that the contents of the list are integers. But `list` alone wouldn't. /// diff --git a/docs/es/docs/tutorial/query-params-str-validations.md b/docs/es/docs/tutorial/query-params-str-validations.md index f378b9dce..9cb76156f 100644 --- a/docs/es/docs/tutorial/query-params-str-validations.md +++ b/docs/es/docs/tutorial/query-params-str-validations.md @@ -321,22 +321,6 @@ Así que, cuando necesites declarar un valor como requerido mientras usas `Query {* ../../docs_src/query_params_str_validations/tutorial006_an_py39.py hl[9] *} -### Requerido con Puntos suspensivos (`...`) - -Hay una manera alternativa de declarar explícitamente que un valor es requerido. Puedes establecer el valor por defecto al valor literal `...`: - -{* ../../docs_src/query_params_str_validations/tutorial006b_an_py39.py hl[9] *} - -/// info | Información - -Si no habías visto eso `...` antes: es un valor especial único, es parte de Python y se llama "Ellipsis". - -Se usa por Pydantic y FastAPI para declarar explícitamente que un valor es requerido. - -/// - -Esto le permitirá a **FastAPI** saber que este parámetro es requerido. - ### Requerido, puede ser `None` Puedes declarar que un parámetro puede aceptar `None`, pero que aún así es requerido. Esto obligaría a los clientes a enviar un valor, incluso si el valor es `None`. diff --git a/docs/ru/docs/tutorial/query-params-str-validations.md b/docs/ru/docs/tutorial/query-params-str-validations.md index 32a98ff22..13b7015db 100644 --- a/docs/ru/docs/tutorial/query-params-str-validations.md +++ b/docs/ru/docs/tutorial/query-params-str-validations.md @@ -291,22 +291,6 @@ q: Union[str, None] = Query(default=None, min_length=3) {* ../../docs_src/query_params_str_validations/tutorial006_an_py39.py hl[9] *} -### Обязательный параметр с Ellipsis (`...`) - -Альтернативный способ указать обязательность параметра запроса - это указать параметр `default` через многоточие `...`: - -{* ../../docs_src/query_params_str_validations/tutorial006b_an_py39.py hl[9] *} - -/// info | Дополнительная информация - -Если вы ранее не сталкивались с `...`: это специальное значение, часть языка Python и называется "Ellipsis". - -Используется в Pydantic и FastAPI для определения, что значение требуется обязательно. - -/// - -Таким образом, **FastAPI** определяет, что параметр является обязательным. - ### Обязательный параметр с `None` Вы можете определить, что параметр может принимать `None`, но всё ещё является обязательным. Это может потребоваться для того, чтобы пользователи явно указали параметр, даже если его значение будет `None`. @@ -321,18 +305,6 @@ Pydantic, мощь которого используется в FastAPI для /// -### Использование Pydantic's `Required` вместо Ellipsis (`...`) - -Если вас смущает `...`, вы можете использовать `Required` из Pydantic: - -{* ../../docs_src/query_params_str_validations/tutorial006d_an_py39.py hl[4,10] *} - -/// tip | Подсказка - -Запомните, когда вам необходимо объявить query-параметр обязательным, вы можете просто не указывать параметр `default`. Таким образом, вам редко придётся использовать `...` или `Required`. - -/// - ## Множество значений для query-параметра Для query-параметра `Query` можно указать, что он принимает список значений (множество значений). diff --git a/docs/zh/docs/tutorial/query-params-str-validations.md b/docs/zh/docs/tutorial/query-params-str-validations.md index 2fba671f7..c2f9a7e9f 100644 --- a/docs/zh/docs/tutorial/query-params-str-validations.md +++ b/docs/zh/docs/tutorial/query-params-str-validations.md @@ -108,21 +108,6 @@ q: Union[str, None] = Query(default=None, min_length=3) {* ../../docs_src/query_params_str_validations/tutorial006.py hl[7] *} -### 使用省略号(`...`)声明必需参数 - -有另一种方法可以显式的声明一个值是必需的,即将默认参数的默认值设为 `...` : - -{* ../../docs_src/query_params_str_validations/tutorial006b.py hl[7] *} - -/// info - -如果你之前没见过 `...` 这种用法:它是一个特殊的单独值,它是 Python 的一部分并且被称为“Ellipsis”(意为省略号 —— 译者注)。 -Pydantic 和 FastAPI 使用它来显式的声明需要一个值。 - -/// - -这将使 **FastAPI** 知道此查询参数是必需的。 - ### 使用`None`声明必需参数 你可以声明一个参数可以接收`None`值,但它仍然是必需的。这将强制客户端发送一个值,即使该值是`None`。 @@ -137,18 +122,6 @@ Pydantic 是 FastAPI 中所有数据验证和序列化的核心,当你在没 /// -### 使用Pydantic中的`Required`代替省略号(`...`) - -如果你觉得使用 `...` 不舒服,你也可以从 Pydantic 导入并使用 `Required`: - -{* ../../docs_src/query_params_str_validations/tutorial006d.py hl[2,8] *} - -/// tip - -请记住,在大多数情况下,当你需要某些东西时,可以简单地省略 `default` 参数,因此你通常不必使用 `...` 或 `Required` - -/// - ## 查询参数列表 / 多个值 当你使用 `Query` 显式地定义查询参数时,你还可以声明它去接收一组值,或换句话来说,接收多个值。 diff --git a/docs_src/query_params_str_validations/tutorial006b.py b/docs_src/query_params_str_validations/tutorial006b.py deleted file mode 100644 index a8d69c889..000000000 --- a/docs_src/query_params_str_validations/tutorial006b.py +++ /dev/null @@ -1,11 +0,0 @@ -from fastapi import FastAPI, Query - -app = FastAPI() - - -@app.get("/items/") -async def read_items(q: str = Query(default=..., min_length=3)): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial006b_an.py b/docs_src/query_params_str_validations/tutorial006b_an.py deleted file mode 100644 index ea3b02583..000000000 --- a/docs_src/query_params_str_validations/tutorial006b_an.py +++ /dev/null @@ -1,12 +0,0 @@ -from fastapi import FastAPI, Query -from typing_extensions import Annotated - -app = FastAPI() - - -@app.get("/items/") -async def read_items(q: Annotated[str, Query(min_length=3)] = ...): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial006b_an_py39.py b/docs_src/query_params_str_validations/tutorial006b_an_py39.py deleted file mode 100644 index 687a9f544..000000000 --- a/docs_src/query_params_str_validations/tutorial006b_an_py39.py +++ /dev/null @@ -1,13 +0,0 @@ -from typing import Annotated - -from fastapi import FastAPI, Query - -app = FastAPI() - - -@app.get("/items/") -async def read_items(q: Annotated[str, Query(min_length=3)] = ...): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial006c.py b/docs_src/query_params_str_validations/tutorial006c.py index 2ac148c94..0a0e820da 100644 --- a/docs_src/query_params_str_validations/tutorial006c.py +++ b/docs_src/query_params_str_validations/tutorial006c.py @@ -6,7 +6,7 @@ app = FastAPI() @app.get("/items/") -async def read_items(q: Union[str, None] = Query(default=..., min_length=3)): +async def read_items(q: Union[str, None] = Query(min_length=3)): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) diff --git a/docs_src/query_params_str_validations/tutorial006c_an.py b/docs_src/query_params_str_validations/tutorial006c_an.py index 10bf26a57..55c4f4adc 100644 --- a/docs_src/query_params_str_validations/tutorial006c_an.py +++ b/docs_src/query_params_str_validations/tutorial006c_an.py @@ -7,7 +7,7 @@ app = FastAPI() @app.get("/items/") -async def read_items(q: Annotated[Union[str, None], Query(min_length=3)] = ...): +async def read_items(q: Annotated[Union[str, None], Query(min_length=3)]): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) diff --git a/docs_src/query_params_str_validations/tutorial006c_an_py310.py b/docs_src/query_params_str_validations/tutorial006c_an_py310.py index 1ab0a7d53..2995d9c97 100644 --- a/docs_src/query_params_str_validations/tutorial006c_an_py310.py +++ b/docs_src/query_params_str_validations/tutorial006c_an_py310.py @@ -6,7 +6,7 @@ app = FastAPI() @app.get("/items/") -async def read_items(q: Annotated[str | None, Query(min_length=3)] = ...): +async def read_items(q: Annotated[str | None, Query(min_length=3)]): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) diff --git a/docs_src/query_params_str_validations/tutorial006c_an_py39.py b/docs_src/query_params_str_validations/tutorial006c_an_py39.py index ac1273331..76a1cd49a 100644 --- a/docs_src/query_params_str_validations/tutorial006c_an_py39.py +++ b/docs_src/query_params_str_validations/tutorial006c_an_py39.py @@ -6,7 +6,7 @@ app = FastAPI() @app.get("/items/") -async def read_items(q: Annotated[Union[str, None], Query(min_length=3)] = ...): +async def read_items(q: Annotated[Union[str, None], Query(min_length=3)]): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) diff --git a/docs_src/query_params_str_validations/tutorial006c_py310.py b/docs_src/query_params_str_validations/tutorial006c_py310.py index 82dd9e5d7..88b499c7a 100644 --- a/docs_src/query_params_str_validations/tutorial006c_py310.py +++ b/docs_src/query_params_str_validations/tutorial006c_py310.py @@ -4,7 +4,7 @@ app = FastAPI() @app.get("/items/") -async def read_items(q: str | None = Query(default=..., min_length=3)): +async def read_items(q: str | None = Query(min_length=3)): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) diff --git a/docs_src/query_params_str_validations/tutorial006d.py b/docs_src/query_params_str_validations/tutorial006d.py deleted file mode 100644 index a8d69c889..000000000 --- a/docs_src/query_params_str_validations/tutorial006d.py +++ /dev/null @@ -1,11 +0,0 @@ -from fastapi import FastAPI, Query - -app = FastAPI() - - -@app.get("/items/") -async def read_items(q: str = Query(default=..., min_length=3)): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial006d_an.py b/docs_src/query_params_str_validations/tutorial006d_an.py deleted file mode 100644 index ea3b02583..000000000 --- a/docs_src/query_params_str_validations/tutorial006d_an.py +++ /dev/null @@ -1,12 +0,0 @@ -from fastapi import FastAPI, Query -from typing_extensions import Annotated - -app = FastAPI() - - -@app.get("/items/") -async def read_items(q: Annotated[str, Query(min_length=3)] = ...): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial006d_an_py39.py b/docs_src/query_params_str_validations/tutorial006d_an_py39.py deleted file mode 100644 index 687a9f544..000000000 --- a/docs_src/query_params_str_validations/tutorial006d_an_py39.py +++ /dev/null @@ -1,13 +0,0 @@ -from typing import Annotated - -from fastapi import FastAPI, Query - -app = FastAPI() - - -@app.get("/items/") -async def read_items(q: Annotated[str, Query(min_length=3)] = ...): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results From 2e788bbbdce346ab53f972cc87ecceeba2299bbe Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 15 Feb 2025 16:24:24 +0000 Subject: [PATCH 002/352] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 9eacfea86..6a3ec5b73 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -14,6 +14,7 @@ hide: ### Docs +* 📝 Update docs for Query Params and String Validations, remove obsolete Ellipsis docs (`...`). PR [#13377](https://github.com/fastapi/fastapi/pull/13377) by [@tiangolo](https://github.com/tiangolo). * ✏️ Remove duplicate title in docs `body-multiple-params`. PR [#13345](https://github.com/fastapi/fastapi/pull/13345) by [@DanielYang59](https://github.com/DanielYang59). * 📝 Fix test badge. PR [#13313](https://github.com/fastapi/fastapi/pull/13313) by [@esadek](https://github.com/esadek). From 08b817a84294a7e606f8437d11ee75fd3ef207a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 15 Feb 2025 17:28:09 +0100 Subject: [PATCH 003/352] =?UTF-8?q?=F0=9F=94=A5=20Remove=20manual=20type?= =?UTF-8?q?=20annotations=20in=20JWT=20tutorial=20to=20avoid=20typing=20ex?= =?UTF-8?q?pectations=20(JWT=20doesn't=20provide=20more=20types)=20(#13378?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs_src/security/tutorial004.py | 2 +- docs_src/security/tutorial004_an.py | 2 +- docs_src/security/tutorial004_an_py310.py | 2 +- docs_src/security/tutorial004_an_py39.py | 2 +- docs_src/security/tutorial004_py310.py | 2 +- docs_src/security/tutorial005_an.py | 2 +- docs_src/security/tutorial005_an_py310.py | 2 +- docs_src/security/tutorial005_an_py39.py | 6 +++--- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs_src/security/tutorial004.py b/docs_src/security/tutorial004.py index 91d161b8a..222589618 100644 --- a/docs_src/security/tutorial004.py +++ b/docs_src/security/tutorial004.py @@ -95,7 +95,7 @@ async def get_current_user(token: str = Depends(oauth2_scheme)): ) try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) - username: str = payload.get("sub") + username = payload.get("sub") if username is None: raise credentials_exception token_data = TokenData(username=username) diff --git a/docs_src/security/tutorial004_an.py b/docs_src/security/tutorial004_an.py index df50754af..e2221cd39 100644 --- a/docs_src/security/tutorial004_an.py +++ b/docs_src/security/tutorial004_an.py @@ -96,7 +96,7 @@ async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]): ) try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) - username: str = payload.get("sub") + username = payload.get("sub") if username is None: raise credentials_exception token_data = TokenData(username=username) diff --git a/docs_src/security/tutorial004_an_py310.py b/docs_src/security/tutorial004_an_py310.py index eff54ef01..a3f74fc0e 100644 --- a/docs_src/security/tutorial004_an_py310.py +++ b/docs_src/security/tutorial004_an_py310.py @@ -95,7 +95,7 @@ async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]): ) try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) - username: str = payload.get("sub") + username = payload.get("sub") if username is None: raise credentials_exception token_data = TokenData(username=username) diff --git a/docs_src/security/tutorial004_an_py39.py b/docs_src/security/tutorial004_an_py39.py index 0455b500c..b33d677ed 100644 --- a/docs_src/security/tutorial004_an_py39.py +++ b/docs_src/security/tutorial004_an_py39.py @@ -95,7 +95,7 @@ async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]): ) try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) - username: str = payload.get("sub") + username = payload.get("sub") if username is None: raise credentials_exception token_data = TokenData(username=username) diff --git a/docs_src/security/tutorial004_py310.py b/docs_src/security/tutorial004_py310.py index 78bee22a3..d46ce26bf 100644 --- a/docs_src/security/tutorial004_py310.py +++ b/docs_src/security/tutorial004_py310.py @@ -94,7 +94,7 @@ async def get_current_user(token: str = Depends(oauth2_scheme)): ) try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) - username: str = payload.get("sub") + username = payload.get("sub") if username is None: raise credentials_exception token_data = TokenData(username=username) diff --git a/docs_src/security/tutorial005_an.py b/docs_src/security/tutorial005_an.py index 5b67cb145..2e8bb3bdb 100644 --- a/docs_src/security/tutorial005_an.py +++ b/docs_src/security/tutorial005_an.py @@ -117,7 +117,7 @@ async def get_current_user( ) try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) - username: str = payload.get("sub") + username = payload.get("sub") if username is None: raise credentials_exception token_scopes = payload.get("scopes", []) diff --git a/docs_src/security/tutorial005_an_py310.py b/docs_src/security/tutorial005_an_py310.py index 297193e35..90781587f 100644 --- a/docs_src/security/tutorial005_an_py310.py +++ b/docs_src/security/tutorial005_an_py310.py @@ -116,7 +116,7 @@ async def get_current_user( ) try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) - username: str = payload.get("sub") + username = payload.get("sub") if username is None: raise credentials_exception token_scopes = payload.get("scopes", []) diff --git a/docs_src/security/tutorial005_an_py39.py b/docs_src/security/tutorial005_an_py39.py index 1acf47bdc..a5192d8d6 100644 --- a/docs_src/security/tutorial005_an_py39.py +++ b/docs_src/security/tutorial005_an_py39.py @@ -1,5 +1,5 @@ from datetime import datetime, timedelta, timezone -from typing import Annotated, List, Union +from typing import Annotated, Union import jwt from fastapi import Depends, FastAPI, HTTPException, Security, status @@ -44,7 +44,7 @@ class Token(BaseModel): class TokenData(BaseModel): username: Union[str, None] = None - scopes: List[str] = [] + scopes: list[str] = [] class User(BaseModel): @@ -116,7 +116,7 @@ async def get_current_user( ) try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) - username: str = payload.get("sub") + username = payload.get("sub") if username is None: raise credentials_exception token_scopes = payload.get("scopes", []) From 2b3b4161220da0ed854f0d556aa497a8e3e574db Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 15 Feb 2025 16:28:29 +0000 Subject: [PATCH 004/352] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 6a3ec5b73..7d8ca5ba0 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -14,6 +14,7 @@ hide: ### Docs +* 🔥 Remove manual type annotations in JWT tutorial to avoid typing expectations (JWT doesn't provide more types). PR [#13378](https://github.com/fastapi/fastapi/pull/13378) by [@tiangolo](https://github.com/tiangolo). * 📝 Update docs for Query Params and String Validations, remove obsolete Ellipsis docs (`...`). PR [#13377](https://github.com/fastapi/fastapi/pull/13377) by [@tiangolo](https://github.com/tiangolo). * ✏️ Remove duplicate title in docs `body-multiple-params`. PR [#13345](https://github.com/fastapi/fastapi/pull/13345) by [@DanielYang59](https://github.com/DanielYang59). * 📝 Fix test badge. PR [#13313](https://github.com/fastapi/fastapi/pull/13313) by [@esadek](https://github.com/esadek). From 5451d05bc84fc8888dfc9cd8a9f3b2c3987c751e Mon Sep 17 00:00:00 2001 From: alv2017 Date: Sat, 15 Feb 2025 18:31:57 +0200 Subject: [PATCH 005/352] =?UTF-8?q?=E2=9C=85=20Simplify=20tests=20for=20`q?= =?UTF-8?q?uery=5Fparams=5Fstr=5Fvalidations`=20(#13218)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Alejandra <90076947+alejsdev@users.noreply.github.com> --- .../test_tutorial010.py | 23 ++- .../test_tutorial010_an.py | 168 ----------------- .../test_tutorial010_an_py310.py | 175 ------------------ .../test_tutorial010_an_py39.py | 175 ------------------ .../test_tutorial010_py310.py | 175 ------------------ .../test_tutorial011.py | 31 +++- .../test_tutorial011_an.py | 108 ----------- .../test_tutorial011_an_py310.py | 118 ------------ .../test_tutorial011_an_py39.py | 118 ------------ .../test_tutorial011_py310.py | 118 ------------ .../test_tutorial011_py39.py | 118 ------------ .../test_tutorial012.py | 29 ++- .../test_tutorial012_an.py | 96 ---------- .../test_tutorial012_an_py39.py | 106 ----------- .../test_tutorial012_py39.py | 106 ----------- .../test_tutorial013.py | 28 ++- .../test_tutorial013_an.py | 96 ---------- .../test_tutorial013_an_py39.py | 106 ----------- .../test_tutorial014.py | 30 ++- .../test_tutorial014_an.py | 81 -------- .../test_tutorial014_an_py310.py | 91 --------- .../test_tutorial014_an_py39.py | 91 --------- .../test_tutorial014_py310.py | 91 --------- 23 files changed, 117 insertions(+), 2161 deletions(-) delete mode 100644 tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an.py delete mode 100644 tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an_py310.py delete mode 100644 tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an_py39.py delete mode 100644 tests/test_tutorial/test_query_params_str_validations/test_tutorial010_py310.py delete mode 100644 tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an.py delete mode 100644 tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an_py310.py delete mode 100644 tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an_py39.py delete mode 100644 tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py310.py delete mode 100644 tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py39.py delete mode 100644 tests/test_tutorial/test_query_params_str_validations/test_tutorial012_an.py delete mode 100644 tests/test_tutorial/test_query_params_str_validations/test_tutorial012_an_py39.py delete mode 100644 tests/test_tutorial/test_query_params_str_validations/test_tutorial012_py39.py delete mode 100644 tests/test_tutorial/test_query_params_str_validations/test_tutorial013_an.py delete mode 100644 tests/test_tutorial/test_query_params_str_validations/test_tutorial013_an_py39.py delete mode 100644 tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an.py delete mode 100644 tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an_py310.py delete mode 100644 tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an_py39.py delete mode 100644 tests/test_tutorial/test_query_params_str_validations/test_tutorial014_py310.py diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py index 4f52d6ff7..e08e16963 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py @@ -1,14 +1,29 @@ +import importlib + import pytest from dirty_equals import IsDict from fastapi._compat import PYDANTIC_VERSION_MINOR_TUPLE from fastapi.testclient import TestClient +from ...utils import needs_py39, needs_py310 + -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial010 import app +@pytest.fixture( + name="client", + params=[ + "tutorial010", + pytest.param("tutorial010_py310", marks=needs_py310), + "tutorial010_an", + pytest.param("tutorial010_an_py39", marks=needs_py39), + pytest.param("tutorial010_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module( + f"docs_src.query_params_str_validations.{request.param}" + ) - client = TestClient(app) + client = TestClient(mod.app) return client diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an.py deleted file mode 100644 index 5daca1e70..000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an.py +++ /dev/null @@ -1,168 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi._compat import PYDANTIC_VERSION_MINOR_TUPLE -from fastapi.testclient import TestClient - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial010_an import app - - client = TestClient(app) - return client - - -def test_query_params_str_validations_no_query(client: TestClient): - response = client.get("/items/") - assert response.status_code == 200 - assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - - -def test_query_params_str_validations_item_query_fixedquery(client: TestClient): - response = client.get("/items/", params={"item-query": "fixedquery"}) - assert response.status_code == 200 - assert response.json() == { - "items": [{"item_id": "Foo"}, {"item_id": "Bar"}], - "q": "fixedquery", - } - - -def test_query_params_str_validations_q_fixedquery(client: TestClient): - response = client.get("/items/", params={"q": "fixedquery"}) - assert response.status_code == 200 - assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - - -def test_query_params_str_validations_item_query_nonregexquery(client: TestClient): - response = client.get("/items/", params={"item-query": "nonregexquery"}) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "string_pattern_mismatch", - "loc": ["query", "item-query"], - "msg": "String should match pattern '^fixedquery$'", - "input": "nonregexquery", - "ctx": {"pattern": "^fixedquery$"}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "ctx": {"pattern": "^fixedquery$"}, - "loc": ["query", "item-query"], - "msg": 'string does not match regex "^fixedquery$"', - "type": "value_error.str.regex", - } - ] - } - ) - - -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "description": "Query string for the items to search in the database that have a good match", - "required": False, - "deprecated": True, - "schema": IsDict( - { - "anyOf": [ - { - "type": "string", - "minLength": 3, - "maxLength": 50, - "pattern": "^fixedquery$", - }, - {"type": "null"}, - ], - "title": "Query string", - "description": "Query string for the items to search in the database that have a good match", - # See https://github.com/pydantic/pydantic/blob/80353c29a824c55dea4667b328ba8f329879ac9f/tests/test_fastapi.sh#L25-L34. - **( - {"deprecated": True} - if PYDANTIC_VERSION_MINOR_TUPLE >= (2, 10) - else {} - ), - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Query string", - "maxLength": 50, - "minLength": 3, - "pattern": "^fixedquery$", - "type": "string", - "description": "Query string for the items to search in the database that have a good match", - } - ), - "name": "item-query", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an_py310.py deleted file mode 100644 index 89da4d82e..000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an_py310.py +++ /dev/null @@ -1,175 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi._compat import PYDANTIC_VERSION_MINOR_TUPLE -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial010_an_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_query_params_str_validations_no_query(client: TestClient): - response = client.get("/items/") - assert response.status_code == 200 - assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - - -@needs_py310 -def test_query_params_str_validations_item_query_fixedquery(client: TestClient): - response = client.get("/items/", params={"item-query": "fixedquery"}) - assert response.status_code == 200 - assert response.json() == { - "items": [{"item_id": "Foo"}, {"item_id": "Bar"}], - "q": "fixedquery", - } - - -@needs_py310 -def test_query_params_str_validations_q_fixedquery(client: TestClient): - response = client.get("/items/", params={"q": "fixedquery"}) - assert response.status_code == 200 - assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - - -@needs_py310 -def test_query_params_str_validations_item_query_nonregexquery(client: TestClient): - response = client.get("/items/", params={"item-query": "nonregexquery"}) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "string_pattern_mismatch", - "loc": ["query", "item-query"], - "msg": "String should match pattern '^fixedquery$'", - "input": "nonregexquery", - "ctx": {"pattern": "^fixedquery$"}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "ctx": {"pattern": "^fixedquery$"}, - "loc": ["query", "item-query"], - "msg": 'string does not match regex "^fixedquery$"', - "type": "value_error.str.regex", - } - ] - } - ) - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "description": "Query string for the items to search in the database that have a good match", - "required": False, - "deprecated": True, - "schema": IsDict( - { - "anyOf": [ - { - "type": "string", - "minLength": 3, - "maxLength": 50, - "pattern": "^fixedquery$", - }, - {"type": "null"}, - ], - "title": "Query string", - "description": "Query string for the items to search in the database that have a good match", - # See https://github.com/pydantic/pydantic/blob/80353c29a824c55dea4667b328ba8f329879ac9f/tests/test_fastapi.sh#L25-L34. - **( - {"deprecated": True} - if PYDANTIC_VERSION_MINOR_TUPLE >= (2, 10) - else {} - ), - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Query string", - "maxLength": 50, - "minLength": 3, - "pattern": "^fixedquery$", - "type": "string", - "description": "Query string for the items to search in the database that have a good match", - } - ), - "name": "item-query", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an_py39.py deleted file mode 100644 index f5f692b06..000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an_py39.py +++ /dev/null @@ -1,175 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi._compat import PYDANTIC_VERSION_MINOR_TUPLE -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial010_an_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_query_params_str_validations_no_query(client: TestClient): - response = client.get("/items/") - assert response.status_code == 200 - assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - - -@needs_py39 -def test_query_params_str_validations_item_query_fixedquery(client: TestClient): - response = client.get("/items/", params={"item-query": "fixedquery"}) - assert response.status_code == 200 - assert response.json() == { - "items": [{"item_id": "Foo"}, {"item_id": "Bar"}], - "q": "fixedquery", - } - - -@needs_py39 -def test_query_params_str_validations_q_fixedquery(client: TestClient): - response = client.get("/items/", params={"q": "fixedquery"}) - assert response.status_code == 200 - assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - - -@needs_py39 -def test_query_params_str_validations_item_query_nonregexquery(client: TestClient): - response = client.get("/items/", params={"item-query": "nonregexquery"}) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "string_pattern_mismatch", - "loc": ["query", "item-query"], - "msg": "String should match pattern '^fixedquery$'", - "input": "nonregexquery", - "ctx": {"pattern": "^fixedquery$"}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "ctx": {"pattern": "^fixedquery$"}, - "loc": ["query", "item-query"], - "msg": 'string does not match regex "^fixedquery$"', - "type": "value_error.str.regex", - } - ] - } - ) - - -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "description": "Query string for the items to search in the database that have a good match", - "required": False, - "deprecated": True, - "schema": IsDict( - { - "anyOf": [ - { - "type": "string", - "minLength": 3, - "maxLength": 50, - "pattern": "^fixedquery$", - }, - {"type": "null"}, - ], - "title": "Query string", - "description": "Query string for the items to search in the database that have a good match", - # See https://github.com/pydantic/pydantic/blob/80353c29a824c55dea4667b328ba8f329879ac9f/tests/test_fastapi.sh#L25-L34. - **( - {"deprecated": True} - if PYDANTIC_VERSION_MINOR_TUPLE >= (2, 10) - else {} - ), - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Query string", - "maxLength": 50, - "minLength": 3, - "pattern": "^fixedquery$", - "type": "string", - "description": "Query string for the items to search in the database that have a good match", - } - ), - "name": "item-query", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_py310.py deleted file mode 100644 index 5b62c969f..000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_py310.py +++ /dev/null @@ -1,175 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi._compat import PYDANTIC_VERSION_MINOR_TUPLE -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial010_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_query_params_str_validations_no_query(client: TestClient): - response = client.get("/items/") - assert response.status_code == 200 - assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - - -@needs_py310 -def test_query_params_str_validations_item_query_fixedquery(client: TestClient): - response = client.get("/items/", params={"item-query": "fixedquery"}) - assert response.status_code == 200 - assert response.json() == { - "items": [{"item_id": "Foo"}, {"item_id": "Bar"}], - "q": "fixedquery", - } - - -@needs_py310 -def test_query_params_str_validations_q_fixedquery(client: TestClient): - response = client.get("/items/", params={"q": "fixedquery"}) - assert response.status_code == 200 - assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - - -@needs_py310 -def test_query_params_str_validations_item_query_nonregexquery(client: TestClient): - response = client.get("/items/", params={"item-query": "nonregexquery"}) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "string_pattern_mismatch", - "loc": ["query", "item-query"], - "msg": "String should match pattern '^fixedquery$'", - "input": "nonregexquery", - "ctx": {"pattern": "^fixedquery$"}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "ctx": {"pattern": "^fixedquery$"}, - "loc": ["query", "item-query"], - "msg": 'string does not match regex "^fixedquery$"', - "type": "value_error.str.regex", - } - ] - } - ) - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "description": "Query string for the items to search in the database that have a good match", - "required": False, - "deprecated": True, - "schema": IsDict( - { - "anyOf": [ - { - "type": "string", - "minLength": 3, - "maxLength": 50, - "pattern": "^fixedquery$", - }, - {"type": "null"}, - ], - "title": "Query string", - "description": "Query string for the items to search in the database that have a good match", - # See https://github.com/pydantic/pydantic/blob/80353c29a824c55dea4667b328ba8f329879ac9f/tests/test_fastapi.sh#L25-L34. - **( - {"deprecated": True} - if PYDANTIC_VERSION_MINOR_TUPLE >= (2, 10) - else {} - ), - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Query string", - "maxLength": 50, - "minLength": 3, - "pattern": "^fixedquery$", - "type": "string", - "description": "Query string for the items to search in the database that have a good match", - } - ), - "name": "item-query", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py index 5ba39b05d..f4da25752 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py @@ -1,26 +1,47 @@ +import importlib + +import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from docs_src.query_params_str_validations.tutorial011 import app +from ...utils import needs_py39, needs_py310 + + +@pytest.fixture( + name="client", + params=[ + "tutorial011", + pytest.param("tutorial011_py39", marks=needs_py310), + pytest.param("tutorial011_py310", marks=needs_py310), + "tutorial011_an", + pytest.param("tutorial011_an_py39", marks=needs_py39), + pytest.param("tutorial011_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module( + f"docs_src.query_params_str_validations.{request.param}" + ) -client = TestClient(app) + client = TestClient(mod.app) + return client -def test_multi_query_values(): +def test_multi_query_values(client: TestClient): url = "/items/?q=foo&q=bar" response = client.get(url) assert response.status_code == 200, response.text assert response.json() == {"q": ["foo", "bar"]} -def test_query_no_values(): +def test_query_no_values(client: TestClient): url = "/items/" response = client.get(url) assert response.status_code == 200, response.text assert response.json() == {"q": None} -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an.py deleted file mode 100644 index 3942ea77a..000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an.py +++ /dev/null @@ -1,108 +0,0 @@ -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from docs_src.query_params_str_validations.tutorial011_an import app - -client = TestClient(app) - - -def test_multi_query_values(): - url = "/items/?q=foo&q=bar" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": ["foo", "bar"]} - - -def test_query_no_values(): - url = "/items/" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": None} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "anyOf": [ - {"type": "array", "items": {"type": "string"}}, - {"type": "null"}, - ], - "title": "Q", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Q", - "type": "array", - "items": {"type": "string"}, - } - ), - "name": "q", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an_py310.py deleted file mode 100644 index f2ec38c95..000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an_py310.py +++ /dev/null @@ -1,118 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial011_an_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_multi_query_values(client: TestClient): - url = "/items/?q=foo&q=bar" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": ["foo", "bar"]} - - -@needs_py310 -def test_query_no_values(client: TestClient): - url = "/items/" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": None} - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "anyOf": [ - {"type": "array", "items": {"type": "string"}}, - {"type": "null"}, - ], - "title": "Q", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Q", - "type": "array", - "items": {"type": "string"}, - } - ), - "name": "q", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an_py39.py deleted file mode 100644 index cd7b15679..000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an_py39.py +++ /dev/null @@ -1,118 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial011_an_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_multi_query_values(client: TestClient): - url = "/items/?q=foo&q=bar" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": ["foo", "bar"]} - - -@needs_py39 -def test_query_no_values(client: TestClient): - url = "/items/" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": None} - - -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "anyOf": [ - {"type": "array", "items": {"type": "string"}}, - {"type": "null"}, - ], - "title": "Q", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Q", - "type": "array", - "items": {"type": "string"}, - } - ), - "name": "q", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py310.py deleted file mode 100644 index bdc729516..000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py310.py +++ /dev/null @@ -1,118 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial011_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_multi_query_values(client: TestClient): - url = "/items/?q=foo&q=bar" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": ["foo", "bar"]} - - -@needs_py310 -def test_query_no_values(client: TestClient): - url = "/items/" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": None} - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "anyOf": [ - {"type": "array", "items": {"type": "string"}}, - {"type": "null"}, - ], - "title": "Q", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Q", - "type": "array", - "items": {"type": "string"}, - } - ), - "name": "q", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py39.py deleted file mode 100644 index 26ac56b2f..000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py39.py +++ /dev/null @@ -1,118 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial011_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_multi_query_values(client: TestClient): - url = "/items/?q=foo&q=bar" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": ["foo", "bar"]} - - -@needs_py39 -def test_query_no_values(client: TestClient): - url = "/items/" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": None} - - -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "anyOf": [ - {"type": "array", "items": {"type": "string"}}, - {"type": "null"}, - ], - "title": "Q", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Q", - "type": "array", - "items": {"type": "string"}, - } - ), - "name": "q", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py index 1436db384..549a90519 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py @@ -1,25 +1,44 @@ +import importlib + +import pytest from fastapi.testclient import TestClient -from docs_src.query_params_str_validations.tutorial012 import app +from ...utils import needs_py39 + + +@pytest.fixture( + name="client", + params=[ + "tutorial012", + pytest.param("tutorial012_py39", marks=needs_py39), + "tutorial012_an", + pytest.param("tutorial012_an_py39", marks=needs_py39), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module( + f"docs_src.query_params_str_validations.{request.param}" + ) -client = TestClient(app) + client = TestClient(mod.app) + return client -def test_default_query_values(): +def test_default_query_values(client: TestClient): url = "/items/" response = client.get(url) assert response.status_code == 200, response.text assert response.json() == {"q": ["foo", "bar"]} -def test_multi_query_values(): +def test_multi_query_values(client: TestClient): url = "/items/?q=baz&q=foobar" response = client.get(url) assert response.status_code == 200, response.text assert response.json() == {"q": ["baz", "foobar"]} -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_an.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_an.py deleted file mode 100644 index 270763f1d..000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_an.py +++ /dev/null @@ -1,96 +0,0 @@ -from fastapi.testclient import TestClient - -from docs_src.query_params_str_validations.tutorial012_an import app - -client = TestClient(app) - - -def test_default_query_values(): - url = "/items/" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": ["foo", "bar"]} - - -def test_multi_query_values(): - url = "/items/?q=baz&q=foobar" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": ["baz", "foobar"]} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Q", - "type": "array", - "items": {"type": "string"}, - "default": ["foo", "bar"], - }, - "name": "q", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_an_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_an_py39.py deleted file mode 100644 index 548391683..000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_an_py39.py +++ /dev/null @@ -1,106 +0,0 @@ -import pytest -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial012_an_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_default_query_values(client: TestClient): - url = "/items/" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": ["foo", "bar"]} - - -@needs_py39 -def test_multi_query_values(client: TestClient): - url = "/items/?q=baz&q=foobar" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": ["baz", "foobar"]} - - -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Q", - "type": "array", - "items": {"type": "string"}, - "default": ["foo", "bar"], - }, - "name": "q", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_py39.py deleted file mode 100644 index e7d745154..000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_py39.py +++ /dev/null @@ -1,106 +0,0 @@ -import pytest -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial012_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_default_query_values(client: TestClient): - url = "/items/" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": ["foo", "bar"]} - - -@needs_py39 -def test_multi_query_values(client: TestClient): - url = "/items/?q=baz&q=foobar" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": ["baz", "foobar"]} - - -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Q", - "type": "array", - "items": {"type": "string"}, - "default": ["foo", "bar"], - }, - "name": "q", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py index 1ba1fdf61..f2f5f7a85 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py @@ -1,25 +1,43 @@ +import importlib + +import pytest from fastapi.testclient import TestClient -from docs_src.query_params_str_validations.tutorial013 import app +from ...utils import needs_py39 + + +@pytest.fixture( + name="client", + params=[ + "tutorial013", + "tutorial013_an", + pytest.param("tutorial013_an_py39", marks=needs_py39), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module( + f"docs_src.query_params_str_validations.{request.param}" + ) -client = TestClient(app) + client = TestClient(mod.app) + return client -def test_multi_query_values(): +def test_multi_query_values(client: TestClient): url = "/items/?q=foo&q=bar" response = client.get(url) assert response.status_code == 200, response.text assert response.json() == {"q": ["foo", "bar"]} -def test_query_no_values(): +def test_query_no_values(client: TestClient): url = "/items/" response = client.get(url) assert response.status_code == 200, response.text assert response.json() == {"q": []} -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013_an.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial013_an.py deleted file mode 100644 index 343261748..000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013_an.py +++ /dev/null @@ -1,96 +0,0 @@ -from fastapi.testclient import TestClient - -from docs_src.query_params_str_validations.tutorial013_an import app - -client = TestClient(app) - - -def test_multi_query_values(): - url = "/items/?q=foo&q=bar" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": ["foo", "bar"]} - - -def test_query_no_values(): - url = "/items/" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": []} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Q", - "type": "array", - "items": {}, - "default": [], - }, - "name": "q", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013_an_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial013_an_py39.py deleted file mode 100644 index 537d6325b..000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013_an_py39.py +++ /dev/null @@ -1,106 +0,0 @@ -import pytest -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial013_an_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_multi_query_values(client: TestClient): - url = "/items/?q=foo&q=bar" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": ["foo", "bar"]} - - -@needs_py39 -def test_query_no_values(client: TestClient): - url = "/items/" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": []} - - -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Q", - "type": "array", - "items": {}, - "default": [], - }, - "name": "q", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py index 7bce7590c..edd40bb1a 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py @@ -1,23 +1,43 @@ +import importlib + +import pytest from fastapi.testclient import TestClient -from docs_src.query_params_str_validations.tutorial014 import app +from ...utils import needs_py39, needs_py310 + + +@pytest.fixture( + name="client", + params=[ + "tutorial014", + pytest.param("tutorial014_py310", marks=needs_py310), + "tutorial014_an", + pytest.param("tutorial014_an_py39", marks=needs_py39), + pytest.param("tutorial014_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module( + f"docs_src.query_params_str_validations.{request.param}" + ) -client = TestClient(app) + client = TestClient(mod.app) + return client -def test_hidden_query(): +def test_hidden_query(client: TestClient): response = client.get("/items?hidden_query=somevalue") assert response.status_code == 200, response.text assert response.json() == {"hidden_query": "somevalue"} -def test_no_hidden_query(): +def test_no_hidden_query(client: TestClient): response = client.get("/items") assert response.status_code == 200, response.text assert response.json() == {"hidden_query": "Not found"} -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an.py deleted file mode 100644 index 2182e87b7..000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an.py +++ /dev/null @@ -1,81 +0,0 @@ -from fastapi.testclient import TestClient - -from docs_src.query_params_str_validations.tutorial014_an import app - -client = TestClient(app) - - -def test_hidden_query(): - response = client.get("/items?hidden_query=somevalue") - assert response.status_code == 200, response.text - assert response.json() == {"hidden_query": "somevalue"} - - -def test_no_hidden_query(): - response = client.get("/items") - assert response.status_code == 200, response.text - assert response.json() == {"hidden_query": "Not found"} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an_py310.py deleted file mode 100644 index 344004d01..000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an_py310.py +++ /dev/null @@ -1,91 +0,0 @@ -import pytest -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial014_an_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_hidden_query(client: TestClient): - response = client.get("/items?hidden_query=somevalue") - assert response.status_code == 200, response.text - assert response.json() == {"hidden_query": "somevalue"} - - -@needs_py310 -def test_no_hidden_query(client: TestClient): - response = client.get("/items") - assert response.status_code == 200, response.text - assert response.json() == {"hidden_query": "Not found"} - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an_py39.py deleted file mode 100644 index 5d4f6df3d..000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an_py39.py +++ /dev/null @@ -1,91 +0,0 @@ -import pytest -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial014_an_py39 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_hidden_query(client: TestClient): - response = client.get("/items?hidden_query=somevalue") - assert response.status_code == 200, response.text - assert response.json() == {"hidden_query": "somevalue"} - - -@needs_py310 -def test_no_hidden_query(client: TestClient): - response = client.get("/items") - assert response.status_code == 200, response.text - assert response.json() == {"hidden_query": "Not found"} - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_py310.py deleted file mode 100644 index dad49fb12..000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_py310.py +++ /dev/null @@ -1,91 +0,0 @@ -import pytest -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial014_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_hidden_query(client: TestClient): - response = client.get("/items?hidden_query=somevalue") - assert response.status_code == 200, response.text - assert response.json() == {"hidden_query": "somevalue"} - - -@needs_py310 -def test_no_hidden_query(client: TestClient): - response = client.get("/items") - assert response.status_code == 200, response.text - assert response.json() == {"hidden_query": "Not found"} - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } From e24a5002929245ceb0b5fa8e72f684ad6421b3a8 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 15 Feb 2025 16:32:36 +0000 Subject: [PATCH 006/352] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 7d8ca5ba0..01665ac54 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Refactors +* ✅ Simplify tests for `query_params_str_validations`. PR [#13218](https://github.com/fastapi/fastapi/pull/13218) by [@alv2017](https://github.com/alv2017). * ✅ Simplify tests for `app_testing`. PR [#13220](https://github.com/fastapi/fastapi/pull/13220) by [@alv2017](https://github.com/alv2017). * ✅ Simplify tests for `dependency_testing`. PR [#13223](https://github.com/fastapi/fastapi/pull/13223) by [@alv2017](https://github.com/alv2017). From e24299b2ffb027967c4cedb25dade42121edc17f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Haoyu=20=28Daniel=29=20YANG=20=E6=9D=A8=E6=B5=A9=E5=AE=87?= Date: Sat, 15 Feb 2025 17:33:33 +0100 Subject: [PATCH 007/352] =?UTF-8?q?=F0=9F=93=9D=20Add=20more=20precise=20d?= =?UTF-8?q?escription=20of=20HTTP=20status=20code=20range=20in=20docs=20(#?= =?UTF-8?q?13347)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/tutorial/response-status-code.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/en/docs/tutorial/response-status-code.md b/docs/en/docs/tutorial/response-status-code.md index 711042a46..41bf02a8f 100644 --- a/docs/en/docs/tutorial/response-status-code.md +++ b/docs/en/docs/tutorial/response-status-code.md @@ -53,16 +53,16 @@ These status codes have a name associated to recognize them, but the important p In short: -* `100` and above are for "Information". You rarely use them directly. Responses with these status codes cannot have a body. -* **`200`** and above are for "Successful" responses. These are the ones you would use the most. +* `100 - 199` are for "Information". You rarely use them directly. Responses with these status codes cannot have a body. +* **`200 - 299`** are for "Successful" responses. These are the ones you would use the most. * `200` is the default status code, which means everything was "OK". * Another example would be `201`, "Created". It is commonly used after creating a new record in the database. * A special case is `204`, "No Content". This response is used when there is no content to return to the client, and so the response must not have a body. -* **`300`** and above are for "Redirection". Responses with these status codes may or may not have a body, except for `304`, "Not Modified", which must not have one. -* **`400`** and above are for "Client error" responses. These are the second type you would probably use the most. +* **`300 - 399`** are for "Redirection". Responses with these status codes may or may not have a body, except for `304`, "Not Modified", which must not have one. +* **`400 - 499`** are for "Client error" responses. These are the second type you would probably use the most. * An example is `404`, for a "Not Found" response. * For generic errors from the client, you can just use `400`. -* `500` and above are for server errors. You almost never use them directly. When something goes wrong at some part in your application code, or server, it will automatically return one of these status codes. +* `500 - 599` are for server errors. You almost never use them directly. When something goes wrong at some part in your application code, or server, it will automatically return one of these status codes. /// tip From 7e67a91b08c0076ea09c69f9c52fac630c5cad7b Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 15 Feb 2025 16:33:58 +0000 Subject: [PATCH 008/352] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 01665ac54..fa1ff20f5 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -15,6 +15,7 @@ hide: ### Docs +* 📝 Add more precise description of HTTP status code range in docs. PR [#13347](https://github.com/fastapi/fastapi/pull/13347) by [@DanielYang59](https://github.com/DanielYang59). * 🔥 Remove manual type annotations in JWT tutorial to avoid typing expectations (JWT doesn't provide more types). PR [#13378](https://github.com/fastapi/fastapi/pull/13378) by [@tiangolo](https://github.com/tiangolo). * 📝 Update docs for Query Params and String Validations, remove obsolete Ellipsis docs (`...`). PR [#13377](https://github.com/fastapi/fastapi/pull/13377) by [@tiangolo](https://github.com/tiangolo). * ✏️ Remove duplicate title in docs `body-multiple-params`. PR [#13345](https://github.com/fastapi/fastapi/pull/13345) by [@DanielYang59](https://github.com/DanielYang59). From c868581ce7a108bfbef7c3fb4b42fa663dfdea97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 18 Feb 2025 16:18:14 +0100 Subject: [PATCH 009/352] =?UTF-8?q?=F0=9F=94=A7=20Update=20sponsors:=20Add?= =?UTF-8?q?=20LambdaTest=20(#13389)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/data/sponsors.yml | 3 +++ docs/en/docs/img/sponsors/lambdatest.png | Bin 0 -> 6320 bytes 2 files changed, 3 insertions(+) create mode 100644 docs/en/docs/img/sponsors/lambdatest.png diff --git a/docs/en/data/sponsors.yml b/docs/en/data/sponsors.yml index f9bf33ae9..91b23937c 100644 --- a/docs/en/data/sponsors.yml +++ b/docs/en/data/sponsors.yml @@ -58,3 +58,6 @@ bronze: - url: https://testdriven.io/courses/tdd-fastapi/ title: Learn to build high-quality web apps with best practices img: https://fastapi.tiangolo.com/img/sponsors/testdriven.svg + - url: https://lambdatest.com/?utm_source=fastapi&utm_medium=partner&utm_campaign=sponsor&utm_term=opensource&utm_content=webpage + title: LambdaTest, AI-Powered Cloud-based Test Orchestration Platform + img: https://fastapi.tiangolo.com/img/sponsors/lambdatest.png diff --git a/docs/en/docs/img/sponsors/lambdatest.png b/docs/en/docs/img/sponsors/lambdatest.png new file mode 100644 index 0000000000000000000000000000000000000000..674cbcb893dfec5a29dc72452215734603dcede6 GIT binary patch literal 6320 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 aNn)J05mscJEx>;p5Rc`b$`#9)2K^5%?KE=$ literal 0 HcmV?d00001 From 235300c1d2a7d8a011685adc1c9aa2c195c0368f Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 18 Feb 2025 15:18:42 +0000 Subject: [PATCH 010/352] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index fa1ff20f5..c61e9d7e5 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -41,6 +41,7 @@ hide: ### Internal +* 🔧 Update sponsors: Add LambdaTest. PR [#13389](https://github.com/fastapi/fastapi/pull/13389) by [@tiangolo](https://github.com/tiangolo). * ⬆ Bump cloudflare/wrangler-action from 3.13 to 3.14. PR [#13350](https://github.com/fastapi/fastapi/pull/13350) by [@dependabot[bot]](https://github.com/apps/dependabot). * ⬆ Bump mkdocs-material from 9.5.18 to 9.6.1. PR [#13301](https://github.com/fastapi/fastapi/pull/13301) by [@dependabot[bot]](https://github.com/apps/dependabot). * ⬆ Bump pillow from 11.0.0 to 11.1.0. PR [#13300](https://github.com/fastapi/fastapi/pull/13300) by [@dependabot[bot]](https://github.com/apps/dependabot). From e157cf4b9625b2ccdd6a3214ba552bcde9452912 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hyogeun=20Oh=20=28=EC=98=A4=ED=9A=A8=EA=B7=BC=29?= Date: Wed, 19 Feb 2025 01:52:15 +0900 Subject: [PATCH 011/352] =?UTF-8?q?=F0=9F=90=9B=20Fix=20issue=20with=20Swa?= =?UTF-8?q?gger=20theme=20change=20example=20in=20the=20official=20tutoria?= =?UTF-8?q?l=20(#13289)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs_src/configure_swagger_ui/tutorial002.py | 2 +- .../test_tutorial/test_configure_swagger_ui/test_tutorial002.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs_src/configure_swagger_ui/tutorial002.py b/docs_src/configure_swagger_ui/tutorial002.py index cc569ce45..cc75c2196 100644 --- a/docs_src/configure_swagger_ui/tutorial002.py +++ b/docs_src/configure_swagger_ui/tutorial002.py @@ -1,6 +1,6 @@ from fastapi import FastAPI -app = FastAPI(swagger_ui_parameters={"syntaxHighlight.theme": "obsidian"}) +app = FastAPI(swagger_ui_parameters={"syntaxHighlight": {"theme": "obsidian"}}) @app.get("/users/{username}") diff --git a/tests/test_tutorial/test_configure_swagger_ui/test_tutorial002.py b/tests/test_tutorial/test_configure_swagger_ui/test_tutorial002.py index 166901188..d06a385b5 100644 --- a/tests/test_tutorial/test_configure_swagger_ui/test_tutorial002.py +++ b/tests/test_tutorial/test_configure_swagger_ui/test_tutorial002.py @@ -12,7 +12,7 @@ def test_swagger_ui(): '"syntaxHighlight": false' not in response.text ), "not used parameters should not be included" assert ( - '"syntaxHighlight.theme": "obsidian"' in response.text + '"syntaxHighlight": {"theme": "obsidian"}' in response.text ), "parameters with middle dots should be included in a JSON compatible way" assert ( '"dom_id": "#swagger-ui"' in response.text From 286fd694eaf006481c9fdc59a8324e4405fd0683 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 18 Feb 2025 16:52:41 +0000 Subject: [PATCH 012/352] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index c61e9d7e5..5f3d98e5c 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -15,6 +15,7 @@ hide: ### Docs +* 🐛 Fix issue with Swagger theme change example in the official tutorial. PR [#13289](https://github.com/fastapi/fastapi/pull/13289) by [@Zerohertz](https://github.com/Zerohertz). * 📝 Add more precise description of HTTP status code range in docs. PR [#13347](https://github.com/fastapi/fastapi/pull/13347) by [@DanielYang59](https://github.com/DanielYang59). * 🔥 Remove manual type annotations in JWT tutorial to avoid typing expectations (JWT doesn't provide more types). PR [#13378](https://github.com/fastapi/fastapi/pull/13378) by [@tiangolo](https://github.com/tiangolo). * 📝 Update docs for Query Params and String Validations, remove obsolete Ellipsis docs (`...`). PR [#13377](https://github.com/fastapi/fastapi/pull/13377) by [@tiangolo](https://github.com/tiangolo). From 70137c0f7d9534b30ef4ec39f3cea471ade9e567 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 18 Feb 2025 19:44:00 +0100 Subject: [PATCH 013/352] =?UTF-8?q?=F0=9F=94=A7=20Update=20team:=20Add=20L?= =?UTF-8?q?udovico=20(#13390)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/data/members.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/en/data/members.yml b/docs/en/data/members.yml index cf016eae1..7ec16e917 100644 --- a/docs/en/data/members.yml +++ b/docs/en/data/members.yml @@ -17,3 +17,6 @@ members: - login: patrick91 avatar_url: https://avatars.githubusercontent.com/u/667029 url: https://github.com/patrick91 +- login: luzzodev + avatar_url: https://avatars.githubusercontent.com/u/27291415 + url: https://github.com/luzzodev From 8c9c536c0a277125ca95c0d9ef19e2c6a39d1db8 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 18 Feb 2025 18:44:23 +0000 Subject: [PATCH 014/352] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 5f3d98e5c..58e6ca6be 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -42,6 +42,7 @@ hide: ### Internal +* 🔧 Update team: Add Ludovico. PR [#13390](https://github.com/fastapi/fastapi/pull/13390) by [@tiangolo](https://github.com/tiangolo). * 🔧 Update sponsors: Add LambdaTest. PR [#13389](https://github.com/fastapi/fastapi/pull/13389) by [@tiangolo](https://github.com/tiangolo). * ⬆ Bump cloudflare/wrangler-action from 3.13 to 3.14. PR [#13350](https://github.com/fastapi/fastapi/pull/13350) by [@dependabot[bot]](https://github.com/apps/dependabot). * ⬆ Bump mkdocs-material from 9.5.18 to 9.6.1. PR [#13301](https://github.com/fastapi/fastapi/pull/13301) by [@dependabot[bot]](https://github.com/apps/dependabot). From 001473ab66dfae4f56365c61d50432dad5bbb2fe Mon Sep 17 00:00:00 2001 From: Aman Kumar <62641343+bullet-ant@users.noreply.github.com> Date: Thu, 20 Feb 2025 19:39:14 +0530 Subject: [PATCH 015/352] =?UTF-8?q?=F0=9F=93=9D=20Fix=20typos=20in=20virtu?= =?UTF-8?q?al=20environments=20documentation=20(#13396)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/virtual-environments.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/docs/virtual-environments.md b/docs/en/docs/virtual-environments.md index b75be18c3..4f65b3b80 100644 --- a/docs/en/docs/virtual-environments.md +++ b/docs/en/docs/virtual-environments.md @@ -668,7 +668,7 @@ After activating the virtual environment, the `PATH` variable would look somethi /home/user/code/awesome-project/.venv/bin:/usr/bin:/bin:/usr/sbin:/sbin ``` -That means that the system will now start looking first look for programs in: +That means that the system will now start looking first for programs in: ```plaintext /home/user/code/awesome-project/.venv/bin @@ -692,7 +692,7 @@ and use that one. C:\Users\user\code\awesome-project\.venv\Scripts;C:\Windows\System32 ``` -That means that the system will now start looking first look for programs in: +That means that the system will now start looking first for programs in: ```plaintext C:\Users\user\code\awesome-project\.venv\Scripts From 3aee64b94f008c141cc8205ef3ccebf3978588dd Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 20 Feb 2025 14:09:37 +0000 Subject: [PATCH 016/352] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 58e6ca6be..c196a5197 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -15,6 +15,7 @@ hide: ### Docs +* 📝 Fix typos in virtual environments documentation. PR [#13396](https://github.com/fastapi/fastapi/pull/13396) by [@bullet-ant](https://github.com/bullet-ant). * 🐛 Fix issue with Swagger theme change example in the official tutorial. PR [#13289](https://github.com/fastapi/fastapi/pull/13289) by [@Zerohertz](https://github.com/Zerohertz). * 📝 Add more precise description of HTTP status code range in docs. PR [#13347](https://github.com/fastapi/fastapi/pull/13347) by [@DanielYang59](https://github.com/DanielYang59). * 🔥 Remove manual type annotations in JWT tutorial to avoid typing expectations (JWT doesn't provide more types). PR [#13378](https://github.com/fastapi/fastapi/pull/13378) by [@tiangolo](https://github.com/tiangolo). From 6ebe7539080531008a9752e8e050b099eef23885 Mon Sep 17 00:00:00 2001 From: Valentyn Date: Thu, 20 Feb 2025 09:13:44 -0500 Subject: [PATCH 017/352] =?UTF-8?q?=F0=9F=8C=90=20Add=20Ukrainian=20transl?= =?UTF-8?q?ation=20for=20`docs/uk/docs/tutorial/request-forms-and-files.md?= =?UTF-8?q?`=20(#13386)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../docs/tutorial/request-forms-and-files.md | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 docs/uk/docs/tutorial/request-forms-and-files.md diff --git a/docs/uk/docs/tutorial/request-forms-and-files.md b/docs/uk/docs/tutorial/request-forms-and-files.md new file mode 100644 index 000000000..a089ef945 --- /dev/null +++ b/docs/uk/docs/tutorial/request-forms-and-files.md @@ -0,0 +1,41 @@ +# Запити з формами та файлами + +У FastAPI Ви можете одночасно отримувати файли та поля форми, використовуючи `File` і `Form`. + +/// info | Інформація + +Щоб отримувати завантажені файли та/або дані форми, спочатку встановіть python-multipart. + +Переконайтеся, що Ви створили [віртуальне середовище](../virtual-environments.md){.internal-link target=_blank}, активували його, а потім встановили бібліотеку, наприклад: + +```console +$ pip install python-multipart +``` + +/// + +## Імпорт `File` та `Form` + +{* ../../docs_src/request_forms_and_files/tutorial001_an_py39.py hl[3] *} + +## Оголошення параметрів `File` та `Form` + +Створіть параметри файлів та форми так само як і для `Body` або `Query`: + +{* ../../docs_src/request_forms_and_files/tutorial001_an_py39.py hl[10:12] *} + +Файли та поля форми будуть завантажені як формові дані, і Ви отримаєте як файли, так і введені користувачем поля. + +Ви також можете оголосити деякі файли як `bytes`, а деякі як `UploadFile`. + +/// warning | Увага + +Ви можете оголосити кілька параметрів `File` і `Form` в операції *шляху*, але не можете одночасно оголошувати `Body`-поля, які очікуєте отримати у форматі JSON, оскільки запит матиме тіло, закодоване за допомогою `multipart/form-data`, а не `application/json`. + +Це не обмеження **FastAPI**, а частина протоколу HTTP. + +/// + +## Підсумок + +Використовуйте `File` та `Form` разом, коли вам потрібно отримувати дані форми та файли в одному запиті. From 498ba94bfc11f7bb91844162b34fa4ccb4d9f07b Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 20 Feb 2025 14:14:07 +0000 Subject: [PATCH 018/352] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index c196a5197..baf0b015f 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -25,6 +25,7 @@ hide: ### Translations +* 🌐 Add Ukrainian translation for `docs/uk/docs/tutorial/request-forms-and-files.md`. PR [#13386](https://github.com/fastapi/fastapi/pull/13386) by [@valentinDruzhinin](https://github.com/valentinDruzhinin). * 🌐 Update Korean translation for `docs/ko/docs/help-fastapi.md`. PR [#13262](https://github.com/fastapi/fastapi/pull/13262) by [@Zerohertz](https://github.com/Zerohertz). * 🌐 Add Korean translation for `docs/ko/docs/advanced/custom-response.md`. PR [#13265](https://github.com/fastapi/fastapi/pull/13265) by [@11kkw](https://github.com/11kkw). * 🌐 Update Korean translation for `docs/ko/docs/tutorial/security/simple-oauth2.md`. PR [#13335](https://github.com/fastapi/fastapi/pull/13335) by [@yes0ng](https://github.com/yes0ng). From b397ad9e52a8606e4ea1233f6f66bfd221c0e79b Mon Sep 17 00:00:00 2001 From: Valentyn Date: Thu, 20 Feb 2025 09:16:09 -0500 Subject: [PATCH 019/352] =?UTF-8?q?=F0=9F=8C=90=20Add=20Ukrainian=20transl?= =?UTF-8?q?ation=20for=20`docs/uk/docs/tutorial/request-form-models.md`=20?= =?UTF-8?q?(#13384)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/uk/docs/tutorial/request-form-models.md | 78 ++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 docs/uk/docs/tutorial/request-form-models.md diff --git a/docs/uk/docs/tutorial/request-form-models.md b/docs/uk/docs/tutorial/request-form-models.md new file mode 100644 index 000000000..7f5759e79 --- /dev/null +++ b/docs/uk/docs/tutorial/request-form-models.md @@ -0,0 +1,78 @@ +# Моделі форм (Form Models) + +У FastAPI Ви можете використовувати **Pydantic-моделі** для оголошення **полів форми**. + +/// info | Інформація + +Щоб використовувати форми, спочатку встановіть python-multipart. + +Переконайтеся, що Ви створили [віртуальне середовище](../virtual-environments.md){.internal-link target=_blank}, активували його, а потім встановили бібліотеку, наприклад: + +```console +$ pip install python-multipart +``` + +/// + +/// note | Підказка + +Ця функція підтримується, починаючи з FastAPI версії `0.113.0`. 🤓 + +/// + +## Використання Pydantic-моделей для форм + +Вам просто потрібно оголосити **Pydantic-модель** з полями, які Ви хочете отримати як **поля форми**, а потім оголосити параметр як `Form`: + +{* ../../docs_src/request_form_models/tutorial001_an_py39.py hl[9:11,15] *} + +**FastAPI** **витягне** дані для **кожного поля** з **формових даних** у запиті та надасть вам Pydantic-модель, яку Ви визначили. + +## Перевірка документації + +Ви можете перевірити це в UI документації за `/docs`: + +
+ +
+ +## Заборона додаткових полів форми + +У деяких особливих випадках (ймовірно, рідко) Ви можете **обмежити** форму лише тими полями, які були оголошені в Pydantic-моделі, і **заборонити** будь-які **додаткові** поля. + +/// note | Підказка + +Ця функція підтримується, починаючи з FastAPI версії `0.114.0`. 🤓 + +/// + +Ви можете використати конфігурацію Pydantic-моделі, щоб заборонити `forbid` будь-які додаткові `extra` поля: + +{* ../../docs_src/request_form_models/tutorial002_an_py39.py hl[12] *} + +Якщо клієнт спробує надіслати додаткові дані, він отримає **відповідь з помилкою**. + +Наприклад, якщо клієнт спробує надіслати наступні поля форми: + +* `username`: `Rick` +* `password`: `Portal Gun` +* `extra`: `Mr. Poopybutthole` + +Він отримає відповідь із помилкою, яка повідомляє, що поле `extra` не дозволено: + +```json +{ + "detail": [ + { + "type": "extra_forbidden", + "loc": ["body", "extra"], + "msg": "Extra inputs are not permitted", + "input": "Mr. Poopybutthole" + } + ] +} +``` + +## Підсумок + +Ви можете використовувати Pydantic-моделі для оголошення полів форми у FastAPI. 😎 From 920110276a551ca20fb6a9a8a190d242b28a5151 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 20 Feb 2025 14:16:32 +0000 Subject: [PATCH 020/352] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index baf0b015f..59501b554 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -25,6 +25,7 @@ hide: ### Translations +* 🌐 Add Ukrainian translation for `docs/uk/docs/tutorial/request-form-models.md`. PR [#13384](https://github.com/fastapi/fastapi/pull/13384) by [@valentinDruzhinin](https://github.com/valentinDruzhinin). * 🌐 Add Ukrainian translation for `docs/uk/docs/tutorial/request-forms-and-files.md`. PR [#13386](https://github.com/fastapi/fastapi/pull/13386) by [@valentinDruzhinin](https://github.com/valentinDruzhinin). * 🌐 Update Korean translation for `docs/ko/docs/help-fastapi.md`. PR [#13262](https://github.com/fastapi/fastapi/pull/13262) by [@Zerohertz](https://github.com/Zerohertz). * 🌐 Add Korean translation for `docs/ko/docs/advanced/custom-response.md`. PR [#13265](https://github.com/fastapi/fastapi/pull/13265) by [@11kkw](https://github.com/11kkw). From 987d2f9a92bcb8f453a41d7e3230e00cb9a8d8db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 20 Feb 2025 18:49:13 +0100 Subject: [PATCH 021/352] =?UTF-8?q?=F0=9F=94=A7=20Update=20sponsors:=20add?= =?UTF-8?q?=20CodeRabbit=20(#13402)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + docs/en/data/sponsors.yml | 3 +++ docs/en/docs/img/sponsors/coderabbit-banner.png | Bin 0 -> 9522 bytes docs/en/docs/img/sponsors/coderabbit.png | Bin 0 -> 20695 bytes docs/en/overrides/main.html | 6 ++++++ 5 files changed, 10 insertions(+) create mode 100644 docs/en/docs/img/sponsors/coderabbit-banner.png create mode 100644 docs/en/docs/img/sponsors/coderabbit.png diff --git a/README.md b/README.md index d5d5ced52..9a1260b90 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ The key features are: + diff --git a/docs/en/data/sponsors.yml b/docs/en/data/sponsors.yml index 91b23937c..b994e533a 100644 --- a/docs/en/data/sponsors.yml +++ b/docs/en/data/sponsors.yml @@ -32,6 +32,9 @@ gold: - url: https://docs.render.com/deploy-fastapi?utm_source=deploydoc&utm_medium=referral&utm_campaign=fastapi title: Deploy & scale any full-stack web app on Render. Focus on building apps, not infra. img: https://fastapi.tiangolo.com/img/sponsors/render.svg + - url: https://www.coderabbit.ai/?utm_source=fastapi&utm_medium=badge&utm_campaign=fastapi + title: Cut Code Review Time & Bugs in Half with CodeRabbit + img: https://fastapi.tiangolo.com/img/sponsors/coderabbit.png silver: - url: https://github.com/deepset-ai/haystack/ title: Build powerful search from composable, open source building blocks diff --git a/docs/en/docs/img/sponsors/coderabbit-banner.png b/docs/en/docs/img/sponsors/coderabbit-banner.png new file mode 100644 index 0000000000000000000000000000000000000000..da3bb348204a39d83203e4580ad213dddce5ac99 GIT binary patch literal 9522 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 YVD9}kX5O^}o_9i$mQ;`^7dQ0(A0pkdO#lD@ literal 0 HcmV?d00001 diff --git a/docs/en/docs/img/sponsors/coderabbit.png b/docs/en/docs/img/sponsors/coderabbit.png new file mode 100644 index 0000000000000000000000000000000000000000..74c25e88466fa1b5251f7136bb86103cf79e874f GIT binary patch literal 20695 zcmbSzWmgLvVL@x1b3Dg1fsv$l&h5-GW1KcXzox@4EL7+z-R7 z#WX$LeYVuDszcZ}MJY67LSzsKgeD^`t^&MY053B{c;IJqe#ap2hU6rz?Fs^+_d;G! zgO2~10ly@0lhATgb+mBvGSv%Oeni@NqGda3gW}FESfk9aDbD zS>>~rPXa#e z2G#cl!h287^On)9LfeBXU@++gM-i=pw_*63PtI4>^qIs@DO7RX5esAIjn94>yc-zbdZCr>e}E8tdnZ`l~D5k{Zoo_w?HVE zyB+o%-tdWu>6`QQ6o$GA{kG_(y8Vl8n3#*3zoTnW*LJe1>9sh@{OKt6k;cpQe|aBw zu%zRENs(ra6FItFmMVRcZPA0zRVlrQFnozWu5635X?Uv4_JUldr;+IUahHEesj9G2 z{BKO=Bw0PDKleOm%?a8qE}WoUmo(s$$s|pKpR9ZTc(?Cc+}uuj7Hdptl4JwURjo(f z5&8|f{P#J^9}M*j0S?oDRIv6#&6ohh1hnpMWSx_v&W!_#fFGv4g! zcl2W$xQL7KO?|y18kyif?@Q;bayT#D-RE{c+ShKdxz&wkbvyU~kG358_WC^26<}D= z(EhlW5#avvkvzop?QwruDnmm)eyKX6KVL}v z+_*k{YGd)b`0TOPlEpreI`YYLRZMeX036Nlw6Uh=cY9C3}=ZD zQneby1o^fg_|x$$&$mqPYh0(JQbX@iQ?4Rf-Go#4i-${7S5oegB+WOJ{cInyI?GAp zxw5&Nvkv;dC6lmy^+LOeSUlU2rSj<574>dPObAPt_m7KPtnmFVs@buk~^!15&pHHlbUB}{9ea>GQ4BCH{sw$c648af$ zwB4^gU;B;jVzOIg{`vh8q1tR%#i9Lj-)`~Zd*|s#DdG`jDFq-3T}Bz3*aO}~pKg|t z8XLL3-!2FLRT72^{1b%Wd^?x2dtU)$MqBMakkCOcj1PmYt?l-*ZPmu*;69tr-8S&G z0ei<~b9CIRPV5<6_7)Ep#=4)bH-h*N*SnZwgoRQZ{jXO*tA6*6X&i?+py?-e%L${c zKJvwSYbov z3h!V@@TanpZrA0!igu2#wp;_z$%#cxBJ%#`XYJ^?e^OWCJR>uhU`{~9)vlN$)B%;s z?Iz&U5l<6_)%l%QdT_L)PVz1^QFEUO3-2Nzu1;*Poc^H9Nw7YTcQ_Eu9H|ovh5?lKYd*RTxRV)8 zfu_-q9NHGVS3|VKE<@tLHT)sje?6(2yu6GF2p7Z1>KjQaCt%$I!DEoXevQXh6uhN} z9O%%po02$cPNgJNYy5sa(e_#I9y6N96)sZ421GwGaspGzm#0i7}RU?`1IM|Ng4Q_zG`E!D4z4d~m;s(Q#buKe{S)x5YkQ5Sr~J-aVS}zCwA7F)6;CPG)cP-z z*HsMlktLZq;K$=Np34xKDeqz7wl;7xQlziEkO%}4hQ&gjI!QJdC~>N#YF-;ZLU}Ly zwh7pa6O;=i<0(Rb4)C}bfy=lF#QNhVhNIDRK6NVVwtRfw&H|KEKh6S+(bs?w)TNZo z-h0!tRqqPzYvi``PND>A(&2bYrOUKQ;<@ei!6>5M-yeIeZEXwcT(Lp)8tpXPT&$VS z18OAx53I%Vyj&GNVbRgq8q!q;pHPXoeig}O$S^A`2`xFGy#PJuTdCT^d{gIOLCnP3 zo(By4$?!kV^^r77|2QC*>5`B!gdT=8nof_FhZeg0g=`lpw;`oPDd0OJb(WieWBYVJ zcju)&mz`G@edPn0jLE}&z&p1?>mlFw+oO%AV@I>gEy|Z8%`W5ptd*9-V%x{dn0HN> zL$}vkVdKBW3NII-nirwM0z<5W#KTXwM1;}|wu~uyS1_On;di@8Rz2787|_FAQ`fM* z=c@qsb=xSo>FpQk`$K>-Q%cKj^2&9!dXdT|q*-PAIGEoY#8v3Eq#M2-aicvm>E033 zYgSrZY>2FS?@z7|{Bx95B{UCZtFaj43A)@Kyv#b+n|OIdA)G($dS}V@b)kiny4lj4 z$&_`E6@6amMoM7ySv^khpKcDvTiuU=BC7~96@var)N;vv1pVm!S!g1J-te68(jScyc`$A=#Um%-%iB^X5Pu}F5(`Ru@3C*XY*SKrWZ zbx=?TDMDhQ=*FwftQA_9n3F=!#93Jd+U*ZW^Hl*^&f5dkZino^v8=uiEI=TSoGiJ5 zc+PmH=Uw++(t6SPf7vwj_}vSNYc5DiM)CbN?iHGKasbY7I9G03j_Ldy{$l0vI95f(00mH*?xs78Hqm;gdx-;fv3MLUjiVIHS?uSJu9HZ&65Z@ z+1k=~e0Q(5nX}>J^Z29FHog;84B(@4`L&M_P*nWlSEP;c{|SrUW%6e|?BAkSMu1KU z#HDh-Bl1PCURjT4750cDN>#khOC8nq zmasC*HrpKaQZe+_9Ydjf=e0}N-H&9vb+uzLTOnX9=j|rBNpNCkSc(mZ1*Wc-GJA)* zh7eDXT#8Wd%^^DxpOak`P(%k}NJjxQ^>Q24^hNmfut6}n<5Ch2Pw!%WH`!4Wo&V$m zl|p)VRlsYGAYIoQBLUZmm~KZj&HDvL=2kvhqq(0DzzpH*=Td zyul3sb;LYH|F%(XyR!3%4RUdz!Y?)8%JY9dnhssBR32;I)J$zBbQa_B@f|?&I8X3e z*7(036ML@idus9D9xtSXlC)+VS3V;Ft!CTf{C)lXl^W3@2K2-Oj8;>NKww%bDbZF-ldr!_;wEN>G1l1D%>>n`qdrOu%+dmLU&YYuy5HPlYie*{V zac6eA?EbQa*99q)EA7+(0GepCX3%d%wPn;a@J0fX*af7N>co@tvg5V9;%NeagXZ02 z!&--bw#3eTdBna>mO){@i^icW4y*WYr|Xs5kcS3fJxI4G*8oophU;^s05xNrdwrzU zy^0m+NjXomc|cu7gB((%5(eCC25&&-Xm3}-v+EeERPFm)Z}ZIz{cuG+&Jj@e+r3e~ zmbSO7{Z(R4FdypW1*!%lI?B|`AtQxlCTkLfjA&DQ!7$tCr2idhh1VseqVOp;Jo;(| zb&lQ$r|=7Fk}O={-JM5q_V+Knp%?`!rEJd_0XxzBXQeP2r6AZVnS2B-m~V+y!k5k~ zU%DHM(p*v9VDgScNFK8h?r&vav1K1gEWlr~#WriLn#Grjh(1J6FPg?(p&dso)cj+( zIhZD=O&MJUYPR5!54Zs`@}vs+QO)bzE!!#qSu{1Z zfoEpm|Cj`RDhYVI_Jx2PUat~)-sB92)@U-ZJ9A)hB8j*|bRA!v*Gx))KU#I21Vf4k z&<9Ps0|i^2jz$Rwh*?c|n%3V`As7Z|qXYQ8z;hrUh&=RNxgXM5x$rrfO?Y^_XSO)i zu~uP$6y1(nYRKHR+;E)AVGW%?HAl3W<9EjaG?pI78UeEsB=vzwYL;alNX#?Yjjj6UUq$|4|JUcejilY%67U0YIdpCa+Vh75 zDurCXeUC&HU|NX=E(;&hR@rO;*xY}+U^qPK@a74RzMK(l@F^33Rr37D9EmDGZE|?+ zM}yqw^*-~?b5BN?R`on!=77M?=u6TfSf%|8|a?wFSf`DDq_kpsv>U5e4!HK4VD( z!$Hewqv0)&qH{$AQ)^;IfCmvUtwsPyDGEzTAiGG3*%Uy$Gg=0;05Xp6kH|3sQhNuI ziB(WOz`VQe%Vy{Ml^pS?tOpPD4G(Vf2Xl&C=EEa1>uEWA%TvJXZEC`)Yq(#r-WRVqu&93Y#8c<2vnL*s^zVz#FcaNx__9-aukJc#fgC5@>xStX zI4sGT2{KFPyQKi2Yl7=!$LD+Hg5VG4F@hf3QQWQnX_Q08tc~stYsYokd+z(C66Y^K z?f%UgGRoZbWS_HLYd+~b&MCYFP*3SW7-x{qw*>@yLJY#ipzy?Q=QD}TBC`%IhVYBw zqb`}yE`abbZ>3tVzZpc zIs99~ZTSAAiO=KYxHoAiy)eLiY+()i6@BI*R#HI%BUb#UY#?Ux@e@{BK#XOHnm<^$ zbgoU*G0LL z?c z1O$MhXGqFijdsad?$bb!9DtQ{dM|gPkbEHQRJPBVH!#{b{qdGhb$Z}B9}Yny@;a$m zf-hS12 zzv`IE$avWRNLmO(1h5-LI^~xqb)!qRRZx0f7ye|ycZxg zsP&iQ3$}Ju$rdzAb6(D_`>H6u7@W&%c+xuxDF3lYSaE1Ai-aZkadsc0i}E4?phH zi}tTnrhB)~FReJHs#oS)pE$VCeo^EI#gQZ*4SLL=$;s%J6wCiS9aZeonb|y+fGvO0 z?9c{eu7&Hewc&22Yw=}6RaP3aVf_Kp1biqfsU49DGcTL;hvP~irZ3t%kp0G7%X=C@<#qwvAO z!NyONi6T8FfGvs<;>#FiTJgwi^{NEuyq)5V%JKED4-kG9(xlG)&UIJ6A1htCcAd2n z9TRHPI&?mAL3+jD{+PX0q~V5)vaIu;;P;+>JOJXZep$GY)WlnOKcG4b*zo)icsF!z zdD(&$yZ_k<*bs%v`zjy|31$)8*m zb)6zGxRZ?Bz1!(MTrHZ)LsOvo88Y79{e-}KAuFpTXSc=_381I@oYW8j6S@EBL=pfz z0WR1i62kX19%3~$or?o>TNAGVaG2lI4KPA$HhQ;vDFR5#XMrsjU=wmX{HP5Ku5Dtr zJrG7TG&mTJ=cYxZ2AL`hUfGgtl6tTRgu=Us?!f`44plh*&)K6*9&}MCzHbkB6+J!H zG?DW+$889}fdH;PUKpHbyzh~QQu`;OyAjs^nE^Q z1dIE~n1Y}rBZpW_F4*>WvCmEE_G772MeE8|+bf)}zi@`a>07c2Xn&sMjK&?aB<}Is zqhS+EbeXXKrEv^~mmdE6!y{{i>3z4yb}fn3zva7?#T$E>d#?kot%85ftFD;?un}8l zWh;vAU$IZ4d;CB0ccM@DwuQLE#g;$)%FnkLAB+KkVU!1)+&_od#yTIh=#&|oP%&&T4J9E8AGBMXPT4t*KRWLmQ<|>7#h(CPjGY9n^0+ zVK<6&qS~C+#|U00a`YZ8v?ef7EYV$*kmH}Oan5{|4Py7dZPi`O#D9ZkZnwEJgP7T- zqV~V#{HF$E`w!$qcpq#bc;Q3W2`YZFBO)DY4JuR@pXAM1AiG zT;pG&a=@D;duRCE7v#BB^^|Mx_pOz!6D3FJWZxC`G57VTG`iF2C8WFrANqQ2*u&Td zZ=z|CWNo`=kts9u+Q3(ZazBbLBzg<>W6ZgE2H(%!=E~g7QWz8pS7ZU5pnI+1bJn4FxmRm`gh=K}LBER877{arDc zx_=S;F$&4>nlc)XWmHOA4^u*|hDguyi!0eX`RvHa>Aw;r#e5t$hsro}J~8R6nb8?Q zS|9EjOh6vILI?3d9XV2zR&6m(63T~u_t(uaBioh zon4TYXNG?jh`GpSF_d9xx=WlRr)kydk4=rqFrTyV5gW>6YCrO%4YBp~* zC#rbmv6X!!W@PE3r2Nah7|vOAMm z^)=0#AFDAl+?`1=Hm;Ch1V;ETkih~IZ331$9Ik$trC3fR#~t#}n~M`Zk?}IiG}Q(S zq3_B<7a|Csh5(wdvDIO@9>U-+hL{6*7$RD=xbD9)Dl(@)WQ4vjscK;ISb=qNzztsS zFXJ;s<$gJ!0|;%N*HJ^$f3{U0p)LxJD+_^YSUmm!Uq||mMI$2=E7JBulU8;YbC6gt zt`EPm?GiVU)%UdGFVFX_k03j*S9dOlwMZ^19$hkvQVtsO^sgW56DE}Of=?)mu=zk_ z)N^he%H*H7ga|p{PKmOI`w9~C{utjG{4AQ-O&6ll`t2=^Zt$t0merkf`dAMIIo5qh z25I|YBi#$__uT-$Tl`L-qWsS~E_rCOO;HJM(fXQsLaei{JCax=G8;qERg~$zd`3?A zW8$!6>OcI!$XJR$k(hp3sUE9!227a1bWUi+6K)dP3FS%9ZwBB8u16F5RLs7i15-f2 z>jDIP5%}KK1?Ym-0UgNuZfTc6`)Zb}iS{WsilN2?f_rUkVK#=TQ@(7dqvB5qPhz~C zQ)eTm*9vLe*Gm*(i@~4EnV@EHIqn7HY5r94-69o6?;eX_HlN6O=N_?w`o(&+K}p5U zZ_bbtbDSkcgw^hp6hu-oT?{JxZ(;}5$|JCW1>aE$ER$cfJNW4IwK7D%GKv<}LK5JtIQc>XPz%B%X zbN~`~fSdzp3;~HGS@ZQmt(aGH!CM&T6rfm$1H-~~32U)fCBR4# z83cSvwCMR5YHK*T@c+C3E;}VLxqc-W)lQd~ByX<_ioBOz^X;w4qgAcZFn1|6Z?(Q~ zq%TH}oq&GA=dnZ(SU^QQk&)5F^8Ws73k@8EscwW?^CS(oC2LI{c>S9+BS_*0(w>3N z7fjv>A1L(PzZ_k{%QO|^EJZ5hq@dak+&cZkjgsY7$4uR2;~uiqTEbvgt}0aMi}|_o zz<9O!;M1&e7Mb|-7*BkhUT3!H)L5#+pu43Juy;e|7k~86^V_eGN z6c_LVR&2Jrt9q}*Sq;$Q1=D|E@yM35)JhL zXr#Gyt{krW698Z4blRXAPnIoB&uLlkUJbZ)((7(FgT%jeXD=ptJ&~ z+nAQJtTP*)X@4;84^Po*cKS%2{h+J^4yJAtc3(r-HKH)1NgbvJ3|#MSRC z=x>YfSvmWIk}*4bhHrH8AILm;9x`A9xc9Ik;xXBI7cOQQ5tk(4{_^X^G#t1PK&vto zdw6X`VS}wqow!D&{)Qn5HR38*anKasFK6XBmk#WsvEh{XX=QY-wD~|8hmHGu^8BMW=e=VVB_-z0w3y^#u5&~o`y`vZqS>4`WJ#W0$ojlX{ZVN90 z@d2F?B00a!sq{9#0>noj&}%PRCbJ|PbO3hU`T10MoIZ7V`RJUhb359{>tS=Sv7Gl` zQOZl8c0g)xx8L0!cUMbRjp^XUhFBLfOViax3_dGy?Cglx&lcT8>YVQ(1|574S9nVB z8~iB-!&T=#v6tKEd*O0SuttZqUAgrpT^KSVeF>8SqnYoPP)+OCx7Bfo!8uqU2+Q7C&%{CQ$&Ak(Y}`r}Toza)rQQz{pMo%gA{rMIekj!iB8XI-xR z=LC*5?MTI|ZJ%@HPKPa@<8P8L!~Z2F-KSD~{NSTxf_e4pH}qxU1wJ^~&ILW|cL2dc z(ST81Bt>AMnfP((hstT$ugn(Zz5j_<#5gUeu_TS^Z2!bDYVYO4Lq_{~Sb?*qGu4#)P;FqZQRmM#agqZ>1oQZeow<&sR-O$deu*0lonoIbN6jNoeLl zU&RWV0WpB}`;9aoAD@eA4FKc;{RCo}AQStpL4mV-(M!EQb{$4dGC7vU4k-4$hW3R` zKrODho;dxV&B1#JaAyF`p55%XRSg=E!F~vl!xlh58O;vz(piBiSW(wBD=!DR-!gHSD@Tx4P~IQ)00nZawu@z}x0f=&_m=Ri z*p0BF$*J4D4DY09Njg_# zR{DHZg0x*L_=p}{g;V6f8JRCSbRlf{O7`*-iSXN881n}lWEz6~Uy#p<=@d{lGeBR# z7a9ZHMdCyJ)W0-lzfN;U{ovRY97x>|Zv6%#MJ(T75?AFxO4kT#{^0!nb<0{{jW7ON z9~s%F>B>uXq=ev$HRt>bE9Q#E4=M&@F5R>Wx=Tjv5z5(WC}Bxuut9@-yp;o+9msn% z1Nch$0-VP+Uv=|`?50kn9Relog#%3=4QulMQ=^-1yRUTQFT{!Kj`a-4tV6j35AQoy>*2ooM7D$oKvg~m#taguczay9qxVcR5 z{^fapse3%JRPxi^SC!BGkGW8bdII9+;G7oj+D&P}XkQ3?qX@I=e>bR2#K2g`0EAIo z*&@sahV6GgE7)BY8KK*;I#d!4mJeFGUYkYgG^Y5t@<$~(K0+Ua`;R{Jemws}A@h%W zt|yk`S6Pqc4R608To&ve?&Db)9wSCJbNaqC+b<-$D&%xLFpEsMYqInPZ66+IkxJ!w zlFEPL2zT6xHw01TOYJ*Viyz4`Rd2DSrQ(k=jR)F3>|IjuNstS*BUv z|CGi_#u1YN_1=>rcZcI??ro?ehrp|Pjnh}vZr*Y~2rDt(;Mt; zC|){hcC?37DrE2&2@@Q*;Aw0G`upAInp%=oXZ!gF2M!^6HO$Aem43M`J-(CzUvR}s zLc3%QT2PwG5(UkOY}*8L$qb<)i>Jnah^BCz<@d&?R4UYfPpk~B6QB?YqGuedv62W@ z#N;*LX{<$0`2EP_UySqyMhB_!dZ`xFG@uqps1kzv%qr)k6aKpuT=r4H8hhOk_5sTT zNva3BWY?>G8MVPTr6I!VQAAqP)zOtzvmw|O#}+Na4*RK-oag+9!6UIg--5B!nan@a z$X*iK3*60@c%@Y&oiyMbag|PigEFKYF{p~X**+!{ncII73%~Y1vj&7me*w&Zc!AGn zKGjlL=Y~Yv<%U_q_gw+3>(6U&pqID%x>R;chci+KIi_|ei~sp{^gNc-A#N+E=Dl=M zz<~qdE(sw%55U&Ocb^7T+4UwlU+EVWhJ|6dGxQMxGJ$%?v-!&uDpfcDs_X6c)w`fA ziC)un%^R68~T3vj8 zI;-JVV(?3U_t}xsYCU1j#U>>b`0)F!+CuB$bu~O?Q3VWt%}F5*%dYuiY?!0|^S%5p zc9B@-oCKR(kHBM6=?BJn6I@%ym>5ruF|%{H|70SuWrl_=xl+$}hVh)G?SEp}eWH>v z%O7UYv6hz^Nco_McQy9 z8M?scB0XG%6FbC@NVE;AGDkg0p7r=hK9qF*AoDEPsd>@Oj*`N}UlDYiBa(R?C0n_i z3NEz~A5sSRGi$B-_LY))TBLo=Q^gCSO7{AGO4sLOj@$L~kM3wyr4$wLwH; zfXrWOaCissJs>XGmp61`Q4uR@rntFAx;*Z#Z>r|-MaBfY{C)u*xknxtoWyBs;HNw0 zm587JyFNhdPI>B^^7g#nH+AZGXe*awP5O~tT#pqnMpdP*5@$ISofWHk%y|fhBv@hM z;HYnC_Jay#1JXZEP~?CvJ(bgM1Y6ayZWZHzoI#)6L{3&EoqBgOS27E=-8554l;?7o zm5LyHd}-?QzJ2w0R9vc%Y78U!iyz?rU)Uq5C%NsTuiEE!R;bjSh05t1tp9!e(jVfa z#hQc#cGi|8JQK0m@uZb^TH{9*dO;Ve5$)bH`!uo3P&Z&|OAvo1V+LF-;dIYtc*nI>LTuvCm{c1*My%@Z z(sJgA&hv{$ROxjCu(&HeGZb+w3N4W?=oJNebenaNeOBMlNlpF~^%?m^0fL3@3}%ry zs7!#zTy#)cDL+8~Hv=Rq*IG2nrlYGo1#6L(?74EH zpTgPpcL6HKT^;Uo_X-7RWmIhqxGPx##+q>ka@yfeo>19%y_FCn^NRpR?~GI{Mp%t8 zUZR(++!g~U{0R@%%A9%k_g6livJ4~=|UC?otFGXcowW>1Kh>QN7!rSs4l zVC4x_+4%1^6oE|?fBLv($JEXlWG=kzH44m7OXo#akhP0Ms9-5!GvKSass{o`S!9)G z2vg?73*e2)bB8Y#D-gvC_%e(W@p|MVp2_Z);%Q>;wTIRlH|&QdJ>ofj7FbBoqY%tj zNPfRB%2g)UGIw@s#jXj=wc;8Ts|q_zP}V=u!>x)UCdo1ep zpMia05NQQ8ZN9>VxtJRgI0)C>`(QZKCN~wiG}7VogNa7c2Ll-cWeP&xab{YbPdac{ zRH>CZ)`LYwx4BH?<&sI+xA;}qGza!jhKPz6Wk|| zmv(DW3;a?)v&)RS;T8za=|pi{dO6lKfE;#V+xamO97a}Rjp#frb0tVip;f}m)jF#l z;)GJO<9CcAkv2(O-s^lkB|&cTZ}%$k$>Ir``<->fOefpNR4rFgmc$M2X5prnKNL|X zT15wv<>4DO#3-q9DtuD~)W($19Ri~Z)zi0Z38)H1Z0tYR;oD1#uCYxNWIfatxyUK< zQ7->_C~tgS1m)2xB&fe7ei!l&lhORIVP$mWWAK z+KzX6ZZAn|FG3C7%rUKH_{U17U^Wn}&MKLJAH){DoXlv&VXcC0ovcBZ@Fg&*XTFSg zx)>|CZlk6LiBHa!E;+_Emck@y)~=(ky7b4o!tkFs=Gd5nP~5F@Gz+)c?{H`*KP?#2 zdzSGD=pQor=qSNOU&)A7X^oSD{S=&~k;(p?+-Zj}C4Et6cs_{r%q)Ju&yeryqDtNH z20KL3hpw)t?) z`MYY)Oq7MN&0KCG62;r1D6(3E90`uZE3{mFT1Vbqw;bBIts-Qa#MahKJE||RiL+w# zSH~T}#^n#pz?8GHx|vfJj9Ov6Xvnlm|BWxCB%GcoatU{drl5uUX0xm_`e#xtaRgi_X?i7K zdRWDP(UzZCZ)A%zVvQ6;VM}BFjf|IkPV(hsBeX>3=VaM0x}6O(@rI;OK3~r7ng6`RLcDEOp~?N`UOS5Lmp}dS z&!l?ZqJBKP{O2CEvvL#}N{lM($h@}Zq4snT(RWFY}+{FpQ{W4mNeR|BT+$iJT($qNV`T`YePwmsw+!rry)@yIc82;yQ0@o zE)6_`_*qWN7s{F5e`uw?%0%9X_R@kvT!IlbW(hm#a)%$nmE*~ZppmdR%_ve`MCopv zFar^;=Ucv+P#4)N!`M=q+tRVD#ix>ey+_00GjlNDQumgvsFYJ1M0@%8W*!bMvfAb(O+;rA7<3yXAEhY%jw63 z0r;Pz!9T;D91~+CaE03QCD=L>9c!n~v&9o|O;T^bT+y@2Z~RY%thn_4ieDZ_`GgK; zid0f9FM~~ZhO{#VUG$UT>Trj~^^tVNuNgE*cx_o_Iu=1iLydMwrAafvWVE_9d*yf>k)q1sSb*v1Q=%N1k)t&3%JH(&Z5u85tLCl2o+67DsAv*GMH# zvc-RM%c~c8j!zWVL7}th{cnjWC>C0OsT9!1TUU1#F@?cw}n)O*&(xr{| ze%)b}x&|F-G_)kGQrz*#LEY}Kl&fgaDj-n-hQUcsW~8l8;rCZ%nnYnlZ8{3_P8l*1^74MD z63?ur$jr-1zDD#(09mlcYu5l6E*;cqz%X$*%R5>b8JB!Gsv6WtRGj$@Ss2V{e|eRu zX`|qEOXI016R#JSapPU&{u7-kl#$KnXkgT(Y48Unq974c>S+`tCil^aZ2C&t>R>MP z@M0#+%q)v)l*XD9fOq4qB(bSDuz)RHi#EX&sb>ue8CKS``}4umDmDjnx>HQ@k#pe> zrD-^>%Q8?uJ=2<*=VhHGlj9oezk&(z&4Wj6K9uDP~F?BUYpA#-eWvLJ;F_L6WM1oS+_`)Qv zZxdyp90aLX=1gn~1AB1edA3R@b|di4vqn{^J6vOn|Ey9eO=4qp&{eozD5xWb;+tM* zqh%%b<0Hv?)3Qv-espP~TulPxf9u$=cor4f)S5K#M*RmJI*D*>S#u}coZvnb<~jAS z>4yryozSB}2i&Sk&sX#$X)*rY3S!};Yy&Clkv(dmj_g(Py0T$&at#aqUU$Egb{r}@a3GwQc@U>v_aW0>9^&9&dm zl#yUK$8e*!OU=A$ef&DX3)wj`T_APUgTnl#Fi|9;2G!~95;7uFK9IiBO7KY)y(xKv zEH&mURm{7jFt>$BTxg!fJo;}mizf;xdzy83BkE&cu!KA6*50~OG zn9Z(&^F!GZ7O)#VE(4 zABr0PRF*D5%RmaM5Yv>goL<`9O4;I6eB7uH#u_vSu17XD2ma8w?yko|H z5Y&F&3Dee~dh5G|wT;hZhuEOQ7_h`K1XK4{(H`(*CeEM|^905Ov?p}>d2YAtXP-Zo zjXK1eb4K%QVMP+&egt-5^t(<9#@uJe`0jup&fOQDc?-xV2L9TumhE@T-vQV0vtwB> zTtV63{pyYSjt(u;{eb9U0DUaqx% zw_TWe{E}S~$T3C$HTAfH@Hq$8gp@WII}K$>>`G>eBo38bM_%hxOxwiF4Jon}!e*OuId*y@&?k^gzb=DXqGU#Xq7r~BpO2X z`*~CQBj!+r>ehxtIwxMbC<@1VsUON{NM{&1p*1Wo8>s@-Ns0Y$IBO?fmG)3QUQzH5t<5KC^MlXFFkol=|X;@m@+LF^#b_by1cJq~F0X@Gq3JQ< zIYXyL!Uv6Jc-?a4a`paIjPy2*rJv($dJ2toX=aok6})VRd)R^nRvR@a6)+Rgy?eEB z&e+?%y@zrOsaXeH2HV%X1+`5D}#o;V48s0O3kYo0U} zvAkuY`)!eUXl>!5F}#Vhy*#f$$~M%cfyDyNH>3xtc~}%)FT@j97Yt|57(fpe6?+nY zOPzqP`z}Q?D_f-E#kKpx=XS0EVjcz@dcF_6y$~67Qs9~a;^NQ#ZbPdi^jL2;2Lg7A zz|L3d%K^rxG|pq^XkmXgz;JB_xc32#4FpJa5MwA50_t{YN%BXcYHKjCUoT*5My_h- z7glud^8ZK;->1JhTMY|u9_e>r3IsC_^*rUjcZ-Yruo^)v>5k+U)E`V*^Ck2TTxb-w zDFzq4uMje0-JlFcUK649Ek`<3rGq?%t5xWdaYnXDP(E!%bO>yeF$s5ix-fZ+CV%j` z6D`xuSRlmWsQ#xCr?b0pyix(b97jV=h7G>pshGUz3{fUTvD%;FlW1lRo?&9{Ya#%4 z0~FQ5mEi1BMuUyomOX&Krw9n!Ji)K9G@uMigDsNThE%;tBo=bhh`tNL`TT0 zG1II=mCnYpifM}DGN?-=Z|U8lxyns4YDvIVZcMK}-K}wlGoBrMY5VBj%FsJs=KaoK z$zIEdGkrj|N1NUlGR|hBu8iD*aax2^?tXg>R;QmWQ!krCo}d3ugC4U}rJB-89tp;~ zwn7rDk*BkQDL+M+G*0B)7;=w$&XC0DXi~@Mm7`qlFwiECZ>Wg{YD>3LP033nFSi>o zSGUvnqrttckK;&x0GnC9QHv}a9G<_7n+XoL(8L{bsthkz@8i1wV&9}(7q*Zobq0xs z^3n*AHbM5~-_TRrSQxwoLIeS1w^@Hg%1$2PKC#c~Tu#T`a)R)?w-B&T7I3QqHom=p z_j8Gp*79}*V3W6DVL?F_8d2*v*_u%%{>x=sB4AfL42D1;APHPwjaKbs(WWr!M$ZE$ z>+reXn$ujk`h#(2e7d}Fz#J(A{O^Dd@{s=v@YhlkVxN{0x-tfmXYgpw7UDjbuSyYp z0ZiqWo=DWU(g~685>0sO0a=!jmje{QUnNt_?rpRvo zU=O6X8tOBskA+n^kzDSYKc2@zB`-XL>@wLbh@S9ylvM1yqx@Fj>C*DUuIuE``L5@U z!)GnM-KwG^3bRXr_K%rkSG=~$DSDQ|h!IRVY&ySb=woZk(p5CjXcp<_2|Ggd)WUS` znM8A#RV2oZQQ8k@SguSbvrMV+n{v1CzP?;TMwMIw@yBqqx`d1hv(s-_q95*DD!7!z z)mYpTwq^u*D@9FY#>J8-W{z$t^ib;uIGQI0X~84 zXkk%*cN-`J5NI*AgV!&8#M(aLZ1>ktHRHR?+}6nFohmv9=vOh~YNU&rWB$RWa{K%H z`PXZf6y@#9p^1(K>rHT#CRM-vk>f|ym0(F=Z_aQi1O2BSN#vgUuPVWXVH_V>wqU#A ztvxJ7=i-n%s)fEeAkB%)EYQtiYAPfmOFR;g(xeo^GRMi5)F#(Y#cR+w(o(C`$J(GV zVTBMLVr8jwsWQnlQ{#$IK>3ag6A`sX+;-)o{S_B=i2I?~^Wc?&_pMK>`k{u5??b9R zG>kpw5M4vJJ0Id+Gh zLx%f`E?^@fHwYR(@2k>2GCu}JFsnM0S`^xHsqJ5_ld7Z9M6TPElr-oEwhV5V-w6^V zmB{Hy4w1b7wFVFY0;()-%JWiQf}~MX61oW4c@Z>qpJGQ6W{)`aW|2w!9{x(Z4Ri2I z%@{M=s8C&fq0H3kOiM9kvl_wothpd_#0j8KOsVof)2EgZfT|>X!dUqhGCQu3Hw1FMuwz z++xenM?3l&7{lPGFk>2K7(~iF5jTqM#2PVCn4z_BnrK5}plMts36DPUga@j(qR&)k zp?_j)RYJXd?LyNr7{(WBH{f{MtYuOYDg9m}eo0K8LsHlI>!ZEzsil7<96O&)02oy# z^outh(RkX_yUYk|RQzJKOLTI5VpAj|b?L8wo>rH1*w~MaNtML#R*aMz4EEM^xxEQ= z!99DQ7xHNJ^LLAkh3X0=WoU-sUAYIMJJ9T{K1l{U5#;T02WoP-P~|aEf@1zp0HO?C z^S!4D9Vl@wgn$BHkqJ1p4$%Xgbm<5LVrJznht4_4&%x(80nRl6{N6|eXCa)_kl@lC z$PMOnodSz}oa@a1XfR;|bn}iJb!`~{njb^{YiNpxXj>gwWgxzPw_*GcV=j*2DQ1+_ z&HZ`-MeutGLxkN`D6xw%U{C?P;mr|pH5dh@kl3LWq9u`daS~eQBq63Nuk)0i$>q>w zJymi=t%(Ues31}DTL;b7ni#MdsS!=BLmM0AJ*kNe?i8+Cqm;%5crJ9rRJo}v>6;2$ z%_ybCn7RvORQMiZ%PMGgAAWmpCI7}boRxnjQDn7@Bvn`&-`Ws0u!Qby$k1y7_&tzf zUi7y7;O^TN6FGAIThP1>{%hR6L5oKaiRm1*cf+7Q{O$PKm0&(2nNV6uOCPM5 z%hEkWZn4X(n9)A?y}>LeHa75%#CTjL4FMCa;&}n*VoVl@OvuV zK(Q?#N01aj+_+P$Mxw5T3mSRrezh#Cj~M_S*xW|5y+B5HBc>uki{zGzW_5UdN__Dd zxj8OJ7SNM}RK=cG;aDd4GkjIXZiBc#8eBN17O`U1h zN_sRaG3!f4QPNNjZ)qqSX}+$#R0fr_3MR1&C59fOe4aa=?to3;nn%1J7n*e?vC%xZC9Phqr6s9r^VHP4c$ko%oQ8cQjnd|X zzPFB2LMXlnHQd2J*U?Y5!G8|h#O;)p{j$uFmVy}`tIu38oi+GSsT5P(y>DvZ_e|=9 zPp))ixvVU17`_zqun<2J)d0c}59+CP3uD#sU7QfY&mJ1Spq(KHZ+@tPWsn=rMISx( z%z~h)RsCX%)`bhN>_az5O7=-5 zsom5(m=9x*L{YTt1{xQFD3}Q(AEK_ClWHe$xq+F+DnL8C=F9Mo8-@d`x7@Rd*Y_SM z9ttH*H>F9Nn<+igrg;L{`MqaoE}yxv$a{O&h5q;2T^q03_o+`@jt!-_Qs-4PwVJQ1lo^EHC5zgu6!D(7WlC_&c^LNA8z<8&Qd%e*dN+}w@MhK z*7ksGZqq6xe!dY_5*;qZP)x-OBBoZBHr+QHRZ*bO5{XnG7kYWpZwZ0cn5YAQ9_!9E z;%@-|8!c_gMJoe~vdTWUNkM@|83N*x*QyTu8u4~9$&ll#dI2B z0_@_Fe4#3b*1nH*q3bz5|6#578^8ypzOsYcuvFwLIRM5!N-KD?bH2~At-0lC){Zh= z&SLC_y{h_!lS&s)O=$v87xiUw##U(SvIaC1V-9RarxepMV{BGdP2&NBZjYS|(eY88 z0MwkSvQyImMS(QeLA!H~Bk>~uIM7>Q+qk-{2WDYGlUApPjMO>i_vB>(y8VOgMc?i3 zw^gdnW>0M8_yY}AjAm=s#*92GzOet%q9z-2yj9&^XSb)Ni% zr*>W!{09JQNQ)k+pwq7Ia&C1|(q_i06peFFYPuCi3*hSjDQTg3&>N7I*P{WC>i9aj zFryf0@8Mu^`{IBFT6BaPLcprV>n&LEkUgd3ar%{f*A%G9@Gj1z;Dr^h%!x@vP?CzBRe8@MB4 z(s(FQfzvhg)xOS8GpIk%pxR$x-1a=?qI){DxCN&L5Ynct(F_P9%?t;^0Y6Ehn?PHN z;p^net_W&aZt2Y(i#WEllPVwf{UN&cF*sd@)-98yBK~o8?VJ?FW38` zN8_nckySc!3sTyonQXCF?thdv>sEl>m?ZIV!=un1eKw$THt49tF^r=@P(c9d0#WD$(rBaW1Z8UGjM)Oy zSrxI2i=~v$xTByMiY?1?9+(2)Dny=zRp$*CF+%&XMT}LJ0j5qyN(c$97No4Z1BVuA zL=T)26s&UF$96N-2og8o3k3Sl1{|F>@op>+lnybL_X>PghP0a4QxVlQBVa!^!x0?; z8rMyquZhic1X?F+@CrmX902@5)4)5&tJU!P5vZG?6mDh)C*v>`8~AEKec-Kuzhir8 zGyh7J-j3hP+b(EbXz^5$mn4rryWj9|-OgXB!Br4xKJ%FDzZiZkqz65AG*REKWB;Tm|gSlRh&EgKLKAsM>w)&!_W4gJ)Q3#DJaJ9$;E#3J$UWQuYL?D^}|Q zY>^|X7Ha<}rKQ()`@aO)lasV*u{PqrDbd*JB6M>UiZ*s3uJ!wq`{ zw#{+0j#3C(1F%&nCtQgdGGSWlsGGsvSXQqCEGfnf0%}c7y=db?N7oy`->{7>Q${ky zv_D_;*6XXjFPu>0n(J6Y%l?Y00~f7)z38vCr|Z|5FQ3musWTmz^_NYXd68l#h+RUc zF2GLEu5n@m0qUaTnH1udsg$C;25dH|Y|SzATv28e0()V3q_Dtaqw|WHQ^3Aa6ZfD^ z+avJUwOOb<^FMK{w=UoM~}d92KL>R%q7aMq73;I-%hM=+E$x%IFDZ0syD=4 z&)Wj%J36l*z9{`moBjH_Qt5Khui6wYSK!|6tv?Ce9Gea`DA6I71g}M-3#c88`L@tX z9gW;sw_dfcNQcie5Scd(;0WM^VMj1FTNsDl)#r}@O5^-@&^h(n?sR||W6x)hJ~z?$ zp8@O$bO1-%^1!fZYcYh<+PeW?hmuL$z>2Xv-wU@>(#|nl`~Z3B4frpv`1B^@6DouN O0000