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
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
O0000
+
{% endblock %}
From 5c8fa58fd06d6d6bb4ae9fe7ef95894bdc94d4fc Mon Sep 17 00:00:00 2001
From: github-actions
Date: Thu, 20 Feb 2025 17:49:39 +0000
Subject: [PATCH 022/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 59501b554..3b41affa7 100644
--- a/docs/en/docs/release-notes.md
+++ b/docs/en/docs/release-notes.md
@@ -45,6 +45,7 @@ hide:
### Internal
+* 🔧 Update sponsors: add CodeRabbit. PR [#13402](https://github.com/fastapi/fastapi/pull/13402) by [@tiangolo](https://github.com/tiangolo).
* 🔧 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).
From f8878f3a98057c6ab6161d2577fab49087ba7015 Mon Sep 17 00:00:00 2001
From: Sofie Van Landeghem
Date: Thu, 20 Feb 2025 21:53:18 +0100
Subject: [PATCH 023/352] =?UTF-8?q?=F0=9F=A9=BA=20Unify=20the=20badges=20a?=
=?UTF-8?q?cross=20all=20tutorial=20translations=20(#13329)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: Alejandra <90076947+alejsdev@users.noreply.github.com>
---
docs/az/docs/index.md | 2 +-
docs/bn/docs/index.md | 11 +++++++----
docs/de/docs/index.md | 2 +-
docs/em/docs/index.md | 2 +-
docs/es/docs/index.md | 2 +-
docs/fa/docs/index.md | 8 ++++----
docs/fr/docs/index.md | 2 +-
docs/he/docs/index.md | 6 +++---
docs/hu/docs/index.md | 2 +-
docs/id/docs/index.md | 2 +-
docs/it/docs/index.md | 14 +++++++++-----
docs/ja/docs/index.md | 13 ++++++++-----
docs/ko/docs/index.md | 11 +++++++----
docs/nl/docs/index.md | 2 +-
docs/pl/docs/index.md | 11 +++++++----
docs/pt/docs/index.md | 2 +-
docs/ru/docs/index.md | 6 +++---
docs/tr/docs/index.md | 2 +-
docs/uk/docs/index.md | 2 +-
docs/vi/docs/index.md | 2 +-
docs/yo/docs/index.md | 2 +-
docs/zh-hant/docs/index.md | 2 +-
docs/zh/docs/index.md | 8 ++++----
23 files changed, 66 insertions(+), 50 deletions(-)
diff --git a/docs/az/docs/index.md b/docs/az/docs/index.md
index ad78d7d06..fbbbce130 100644
--- a/docs/az/docs/index.md
+++ b/docs/az/docs/index.md
@@ -6,7 +6,7 @@
-
+
diff --git a/docs/bn/docs/index.md b/docs/bn/docs/index.md
index 678ac9ca9..74ee230a1 100644
--- a/docs/bn/docs/index.md
+++ b/docs/bn/docs/index.md
@@ -5,15 +5,18 @@
FastAPI উচ্চক্ষমতা সম্পন্ন, সহজে শেখার এবং দ্রুত কোড করে প্রোডাকশনের জন্য ফ্রামওয়ার্ক।
-
-
+
+
-
-
+
+
+
+
+
---
diff --git a/docs/de/docs/index.md b/docs/de/docs/index.md
index 411c8e969..d239f0815 100644
--- a/docs/de/docs/index.md
+++ b/docs/de/docs/index.md
@@ -12,7 +12,7 @@
-
+
diff --git a/docs/em/docs/index.md b/docs/em/docs/index.md
index 16b2019d3..57be59b07 100644
--- a/docs/em/docs/index.md
+++ b/docs/em/docs/index.md
@@ -12,7 +12,7 @@
-
+
diff --git a/docs/es/docs/index.md b/docs/es/docs/index.md
index db8da6933..c1da5d633 100644
--- a/docs/es/docs/index.md
+++ b/docs/es/docs/index.md
@@ -12,7 +12,7 @@
-
+
diff --git a/docs/fa/docs/index.md b/docs/fa/docs/index.md
index 6addce763..0aa0bec36 100644
--- a/docs/fa/docs/index.md
+++ b/docs/fa/docs/index.md
@@ -11,11 +11,11 @@
فریمورک FastAPI، کارایی بالا، یادگیری آسان، کدنویسی سریع، آماده برای استفاده در محیط پروداکشن
-
-
+
+
-
-
+
+
diff --git a/docs/fr/docs/index.md b/docs/fr/docs/index.md
index 695429008..d25f7a939 100644
--- a/docs/fr/docs/index.md
+++ b/docs/fr/docs/index.md
@@ -12,7 +12,7 @@
-
+
diff --git a/docs/he/docs/index.md b/docs/he/docs/index.md
index 6498d15e1..bd166f205 100644
--- a/docs/he/docs/index.md
+++ b/docs/he/docs/index.md
@@ -12,10 +12,10 @@
-
+
-
-
+
+
diff --git a/docs/hu/docs/index.md b/docs/hu/docs/index.md
index c6f596650..45ff49c3b 100644
--- a/docs/hu/docs/index.md
+++ b/docs/hu/docs/index.md
@@ -6,7 +6,7 @@
-
+
diff --git a/docs/id/docs/index.md b/docs/id/docs/index.md
index 7fdd1cc7a..5fb0c4c9c 100644
--- a/docs/id/docs/index.md
+++ b/docs/id/docs/index.md
@@ -12,7 +12,7 @@
-
+
diff --git a/docs/it/docs/index.md b/docs/it/docs/index.md
index 8a1039bc5..dc8f5b846 100644
--- a/docs/it/docs/index.md
+++ b/docs/it/docs/index.md
@@ -4,15 +4,19 @@
FastAPI framework, alte prestazioni, facile da imparare, rapido da implementare, pronto per il rilascio in produzione
+
-
-
+
+
+
+
+
-
-
+
+
-
+
diff --git a/docs/ja/docs/index.md b/docs/ja/docs/index.md
index 682c94e83..1ba85f8e0 100644
--- a/docs/ja/docs/index.md
+++ b/docs/ja/docs/index.md
@@ -11,14 +11,17 @@
FastAPI framework, high performance, easy to learn, fast to code, ready for production
-
-
+
+
-
-
+
+
-
+
+
+
+
diff --git a/docs/ko/docs/index.md b/docs/ko/docs/index.md
index 8b00d90bc..0df2000fa 100644
--- a/docs/ko/docs/index.md
+++ b/docs/ko/docs/index.md
@@ -11,15 +11,18 @@
FastAPI 프레임워크, 고성능, 간편한 학습, 빠른 코드 작성, 준비된 프로덕션
-
-
+
+
-
-
+
+
+
+
+
---
diff --git a/docs/nl/docs/index.md b/docs/nl/docs/index.md
index d88bb7771..32b20e31e 100644
--- a/docs/nl/docs/index.md
+++ b/docs/nl/docs/index.md
@@ -12,7 +12,7 @@
-
+
diff --git a/docs/pl/docs/index.md b/docs/pl/docs/index.md
index 9a96c6553..0e13d2631 100644
--- a/docs/pl/docs/index.md
+++ b/docs/pl/docs/index.md
@@ -11,15 +11,18 @@
FastAPI to szybki, prosty w nauce i gotowy do użycia w produkcji framework
-
-
+
+
-
-
+
+
+
+
+
---
diff --git a/docs/pt/docs/index.md b/docs/pt/docs/index.md
index 138048f06..9f08d5224 100644
--- a/docs/pt/docs/index.md
+++ b/docs/pt/docs/index.md
@@ -12,7 +12,7 @@
-
+
diff --git a/docs/ru/docs/index.md b/docs/ru/docs/index.md
index 5ebe1494b..a9546cf1e 100644
--- a/docs/ru/docs/index.md
+++ b/docs/ru/docs/index.md
@@ -12,10 +12,10 @@
-
+
-
-
+
+
diff --git a/docs/tr/docs/index.md b/docs/tr/docs/index.md
index 7ecaf1ba3..f666e2d06 100644
--- a/docs/tr/docs/index.md
+++ b/docs/tr/docs/index.md
@@ -12,7 +12,7 @@
-
+
diff --git a/docs/uk/docs/index.md b/docs/uk/docs/index.md
index 012bac2e2..b573ee259 100644
--- a/docs/uk/docs/index.md
+++ b/docs/uk/docs/index.md
@@ -6,7 +6,7 @@
-
+
diff --git a/docs/vi/docs/index.md b/docs/vi/docs/index.md
index 5e346ded8..5c6b7e8a4 100644
--- a/docs/vi/docs/index.md
+++ b/docs/vi/docs/index.md
@@ -12,7 +12,7 @@
-
+
diff --git a/docs/yo/docs/index.md b/docs/yo/docs/index.md
index 3ad1483de..d6aa78b3d 100644
--- a/docs/yo/docs/index.md
+++ b/docs/yo/docs/index.md
@@ -12,7 +12,7 @@
-
+
diff --git a/docs/zh-hant/docs/index.md b/docs/zh-hant/docs/index.md
index 137a17284..81d99ede4 100644
--- a/docs/zh-hant/docs/index.md
+++ b/docs/zh-hant/docs/index.md
@@ -6,7 +6,7 @@
-
+
diff --git a/docs/zh/docs/index.md b/docs/zh/docs/index.md
index d3e9e3112..94cf8745c 100644
--- a/docs/zh/docs/index.md
+++ b/docs/zh/docs/index.md
@@ -11,11 +11,11 @@
FastAPI 框架,高性能,易于学习,高效编码,生产可用
-
-
+
+
-
-
+
+
From 4516a48c7cd068897ee45c715a5d2aacd692b4e8 Mon Sep 17 00:00:00 2001
From: github-actions
Date: Fri, 21 Feb 2025 11:36:43 +0000
Subject: [PATCH 024/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 3b41affa7..83ffa8deb 100644
--- a/docs/en/docs/release-notes.md
+++ b/docs/en/docs/release-notes.md
@@ -15,6 +15,7 @@ hide:
### Docs
+* 🩺 Unify the badges across all tutorial translations. PR [#13329](https://github.com/fastapi/fastapi/pull/13329) by [@svlandeg](https://github.com/svlandeg).
* 📝 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).
From 31920eff623a49ab177ba29f8f795e69b4cbb7cf Mon Sep 17 00:00:00 2001
From: Valentyn
Date: Sat, 22 Feb 2025 17:01:44 -0500
Subject: [PATCH 025/352] =?UTF-8?q?=F0=9F=8C=90=20Add=20Ukrainian=20transl?=
=?UTF-8?q?ation=20for=20`docs/uk/docs/tutorial/request-files.md`=20(#1339?=
=?UTF-8?q?5)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/uk/docs/tutorial/request-files.md | 175 +++++++++++++++++++++++++
1 file changed, 175 insertions(+)
create mode 100644 docs/uk/docs/tutorial/request-files.md
diff --git a/docs/uk/docs/tutorial/request-files.md b/docs/uk/docs/tutorial/request-files.md
new file mode 100644
index 000000000..18b7cc01c
--- /dev/null
+++ b/docs/uk/docs/tutorial/request-files.md
@@ -0,0 +1,175 @@
+# Запит файлів
+
+Ви можете визначити файли, які будуть завантажуватися клієнтом, використовуючи `File`.
+
+/// info | Інформація
+
+Щоб отримувати завантажені файли, спочатку встановіть python-multipart.
+
+Переконайтеся, що Ви створили [віртуальне середовище](../virtual-environments.md){.internal-link target=_blank}, активували його та встановили пакет, наприклад:
+
+```console
+$ pip install python-multipart
+```
+
+Це необхідно, оскільки завантажені файли передаються у вигляді "форматованих даних форми".
+
+///
+
+## Імпорт `File`
+
+Імпортуйте `File` та `UploadFile` з `fastapi`:
+
+{* ../../docs_src/request_files/tutorial001_an_py39.py hl[3] *}
+
+## Визначення параметрів `File`
+
+Створіть параметри файлів так само як Ви б створювали `Body` або `Form`:
+
+{* ../../docs_src/request_files/tutorial001_an_py39.py hl[9] *}
+
+/// info | Інформація
+
+`File` — це клас, який безпосередньо успадковує `Form`.
+
+Але пам’ятайте, що коли Ви імпортуєте `Query`, `Path`, `File` та інші з `fastapi`, це насправді функції, які повертають спеціальні класи.
+
+///
+
+/// tip | Підказка
+
+Щоб оголосити тіла файлів, Вам потрібно використовувати `File`, тому що інакше параметри будуть інтерпретовані як параметри запиту або параметри тіла (JSON).
+
+///
+
+Файли будуть завантажені у вигляді "форматованих даних форми".
+
+Якщо Ви оголосите тип параметра функції обробника маршруту як `bytes`, **FastAPI** прочитає файл за Вас, і Ви отримаєте його вміст у вигляді `bytes`.
+
+Однак майте на увазі, що весь вміст буде збережено в пам'яті. Це працюватиме добре для малих файлів.
+
+Але в деяких випадках Вам може знадобитися `UploadFile`.
+
+## Параметри файлу з `UploadFile`
+
+Визначте параметр файлу з типом `UploadFile`:
+
+{* ../../docs_src/request_files/tutorial001_an_py39.py hl[14] *}
+
+Використання `UploadFile` має кілька переваг перед `bytes`:
+
+* Вам не потрібно використовувати `File()` у значенні за замовчуванням параметра.
+* Використовується "буферизований" файл:
+ * Файл зберігається в пам'яті до досягнення певного обмеження, після чого він записується на диск.
+* Це означає, що він добре працює для великих файлів, таких як зображення, відео, великі двійкові файли тощо, не споживаючи всю пам'ять.
+Ви можете отримати метадані про завантажений файл.
+* Він має file-like `асинхронний файловий інтерфейс` interface.
+* Він надає фактичний об'єкт Python `SpooledTemporaryFile`, який можна передавати безпосередньо іншим бібліотекам.
+
+### `UploadFile`
+
+`UploadFile` має такі атрибути:
+
+* `filename`: Рядок `str` з оригінальною назвою файлу, який був завантажений (наприклад, `myimage.jpg`).
+* `content_type`: Рядок `str` з MIME-типом (наприклад, `image/jpeg`).
+* `file`: Об'єкт SpooledTemporaryFile (файлоподібний об'єкт). Це фактичний файловий об'єкт Python, який можна безпосередньо передавати іншим функціям або бібліотекам, що очікують "файлоподібний" об'єкт.
+
+`UploadFile` має такі асинхронні `async` методи. Вони викликають відповідні методи файлу під капотом (використовуючи внутрішній `SpooledTemporaryFile`).
+
+* `write(data)`: Записує `data` (`str` або `bytes`) у файл.
+* `read(size)`: Читає `size` (`int`) байтів/символів з файлу.
+* `seek(offset)`: Переміщується до позиції `offset` (`int`) у файлі.
+ * Наприклад, `await myfile.seek(0)` поверне курсор на початок файлу.
+ * This is especially useful if you run `await myfile.read()` once and then need to read the contents again. Це особливо корисно, якщо Ви виконуєте await `await myfile.read()` один раз, а потім потрібно знову прочитати вміст.
+* `close()`: Закриває файл.
+
+Оскільки всі ці методи є асинхронними `async`, Вам потрібно використовувати "await":
+
+Наприклад, всередині `async` *функції обробки шляху* Ви можете отримати вміст за допомогою:
+
+```Python
+contents = await myfile.read()
+```
+Якщо Ви знаходитесь у звичайній `def` *функції обробки шляху*, Ви можете отримати доступ до `UploadFile.file` безпосередньо, наприклад:
+
+```Python
+contents = myfile.file.read()
+```
+
+/// note | Технічні деталі `async`
+
+Коли Ви використовуєте `async` методи, **FastAPI** виконує файлові операції у пулі потоків та очікує їх завершення.
+
+///
+
+/// note | Технічні деталі Starlette
+
+`UploadFile` у **FastAPI** успадковується безпосередньо від `UploadFile` у **Starlette**, але додає деякі необхідні частини, щоб зробити його сумісним із **Pydantic** та іншими компонентами FastAPI.
+
+///
+
+## Що таке "Form Data"
+
+Спосіб, у який HTML-форми (``) надсилають дані на сервер, зазвичай використовує "спеціальне" кодування, відмінне від JSON.
+
+**FastAPI** забезпечує правильне зчитування цих даних з відповідної частини запиту, а не з JSON.
+
+/// note | Технічні деталі
+
+Дані з форм зазвичай кодуються за допомогою "media type" `application/x-www-form-urlencoded`, якщо вони не містять файлів.
+
+Але якщо форма містить файли, вона кодується у форматі `multipart/form-data`. Якщо Ви використовуєте `File`, **FastAPI** визначить, що потрібно отримати файли з відповідної частини тіла запиту.
+
+Щоб дізнатися більше про ці типи кодування та формові поля, ознайомтеся з документацією MDN щодо POST
.
+
+///
+
+/// warning | Увага
+
+Ви можете оголосити кілька параметрів `File` і `Form` в *операції шляху*, але Ви не можете одночасно оголошувати поля `Body`, які мають надходити у форматі JSON, оскільки тіло запиту буде закодоване у форматі `multipart/form-data`, а не `application/json`.
+
+Це не обмеження **FastAPI**, а особливість протоколу HTTP.
+
+///
+
+## Опціональне Завантаження Файлів
+
+Файл можна зробити необов’язковим, використовуючи стандартні анотації типів і встановлюючи значення за замовчуванням `None`:
+
+{* ../../docs_src/request_files/tutorial001_02_an_py310.py hl[9,17] *}
+
+## `UploadFile` із Додатковими Мета Даними
+
+Ви також можете використовувати `File()` разом із `UploadFile`, наприклад, для встановлення додаткових метаданих:
+
+{* ../../docs_src/request_files/tutorial001_03_an_py39.py hl[9,15] *}
+
+## Завантаження Кількох Файлів
+
+Можна завантажувати кілька файлів одночасно.
+
+Вони будуть пов’язані з одним і тим самим "form field", який передається у вигляді "form data".
+
+Щоб це реалізувати, потрібно оголосити список `bytes` або `UploadFile`:
+
+{* ../../docs_src/request_files/tutorial002_an_py39.py hl[10,15] *}
+
+Ви отримаєте, як і було оголошено, `list` із `bytes` або `UploadFile`.
+
+/// note | Технічні деталі
+
+Ви також можете використати `from starlette.responses import HTMLResponse`.
+
+**FastAPI** надає ті ж самі `starlette.responses`, що й `fastapi.responses`, для зручності розробників. Однак більшість доступних відповідей надходять безпосередньо від Starlette.
+
+///
+
+### Завантаження декількох файлів із додатковими метаданими
+
+Так само як і раніше, Ви можете використовувати `File()`, щоб встановити додаткові параметри навіть для `UploadFile`:
+
+{* ../../docs_src/request_files/tutorial003_an_py39.py hl[11,18:20] *}
+
+## Підсумок
+
+Використовуйте `File`, `bytes`та `UploadFile`, щоб оголошувати файли для завантаження у запитах, які надсилаються у вигляді form data.
From b1102e2388e36d0b4a3c75c2d5a89656c3e55970 Mon Sep 17 00:00:00 2001
From: github-actions
Date: Sat, 22 Feb 2025 22:02:06 +0000
Subject: [PATCH 026/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 83ffa8deb..732f76438 100644
--- a/docs/en/docs/release-notes.md
+++ b/docs/en/docs/release-notes.md
@@ -26,6 +26,7 @@ hide:
### Translations
+* 🌐 Add Ukrainian translation for `docs/uk/docs/tutorial/request-files.md`. PR [#13395](https://github.com/fastapi/fastapi/pull/13395) by [@valentinDruzhinin](https://github.com/valentinDruzhinin).
* 🌐 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).
From 48676b4f112b7500721bdc239719dd89e3787c82 Mon Sep 17 00:00:00 2001
From: Valentyn
Date: Sat, 22 Feb 2025 17:02:19 -0500
Subject: [PATCH 027/352] =?UTF-8?q?=F0=9F=8C=90=20Add=20Ukrainian=20transl?=
=?UTF-8?q?ation=20for=20`docs/uk/docs/tutorial/header-params.md`=20(#1338?=
=?UTF-8?q?1)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/uk/docs/tutorial/header-params.md | 91 ++++++++++++++++++++++++++
1 file changed, 91 insertions(+)
create mode 100644 docs/uk/docs/tutorial/header-params.md
diff --git a/docs/uk/docs/tutorial/header-params.md b/docs/uk/docs/tutorial/header-params.md
new file mode 100644
index 000000000..09c70a4f6
--- /dev/null
+++ b/docs/uk/docs/tutorial/header-params.md
@@ -0,0 +1,91 @@
+# Header-параметри
+
+Ви можете визначати параметри заголовків, так само як визначаєте `Query`, `Path` і `Cookie` параметри.
+
+## Імпорт `Header`
+
+Спочатку імпортуйте `Header`:
+
+{* ../../docs_src/header_params/tutorial001_an_py310.py hl[3] *}
+
+## Оголошення параметрів `Header`
+
+Потім оголосіть параметри заголовків, використовуючи ту ж структуру, що й для `Path`, `Query` та `Cookie`.
+
+Ви можете визначити значення за замовчуванням, а також усі додаткові параметри валідації або анотації:
+
+{* ../../docs_src/header_params/tutorial001_an_py310.py hl[9] *}
+
+/// note | Технічні деталі
+
+`Header`є "сестринським" класом для `Path`, `Query` і `Cookie`. Він також успадковується від загального класу `Param`.
+
+Але пам’ятайте, що при імпорті `Query`, `Path`, `Header` та інших із `fastapi`, то насправді вони є функціями, які повертають спеціальні класи.
+
+///
+
+/// info | Інформація
+
+Щоб оголосити заголовки, потрібно використовувати `Header`, інакше параметри будуть інтерпретуватися як параметри запиту.
+
+///
+
+## Автоматичне перетворення
+
+`Header` має додатковий функціонал порівняно з `Path`, `Query` та `Cookie`.
+
+Більшість стандартних заголовків розділяються символом «дефіс», також відомим як «мінус» (`-`).
+
+Але змінна, така як `user-agent`, є недійсною в Python.
+
+Тому, за замовчуванням, `Header` автоматично перетворює символи підкреслення (`_`) на дефіси (`-`) для отримання та документування заголовків.
+
+Оскільки заголовки HTTP не чутливі до регістру, Ви можете використовувати стандартний стиль Python ("snake_case").
+
+Тому Ви можете використовувати `user_agent`, як зазвичай у коді Python, замість того щоб писати з великої літери, як `User_Agent` або щось подібне.
+
+Якщо Вам потрібно вимкнути автоматичне перетворення підкреслень у дефіси, встановіть `convert_underscores` в `Header` значення `False`:
+
+{* ../../docs_src/header_params/tutorial002_an_py310.py hl[10] *}
+
+/// warning | Увага
+
+Перед тим як встановити значення `False` для `convert_underscores` пам’ятайте, що деякі HTTP-проксі та сервери не підтримують заголовки з підкресленнями.
+
+///
+
+## Дубльовані заголовки
+
+Можливо отримати дубльовані заголовки, тобто той самий заголовок із кількома значеннями.
+
+Це можна визначити, використовуючи список у типізації параметра.
+
+Ви отримаєте всі значення дубльованого заголовка у вигляді `list` у Python.
+
+Наприклад, щоб оголосити заголовок `X-Token`, який може з’являтися більше ніж один раз:
+
+{* ../../docs_src/header_params/tutorial003_an_py310.py hl[9] *}
+
+Якщо Ви взаємодієте з цією операцією шляху, надсилаючи два HTTP-заголовки, наприклад:
+
+```
+X-Token: foo
+X-Token: bar
+```
+
+Відповідь буде така:
+
+```JSON
+{
+ "X-Token values": [
+ "bar",
+ "foo"
+ ]
+}
+```
+
+## Підсумок
+
+Оголошуйте заголовки за допомогою `Header`, використовуючи той самий підхід, що й для `Query`, `Path` та `Cookie`.
+
+Не хвилюйтеся про підкреслення у змінних — **FastAPI** автоматично конвертує їх.
From b0215699137c41a96c57f650640cb9db1c25314a Mon Sep 17 00:00:00 2001
From: github-actions
Date: Sat, 22 Feb 2025 22:03:09 +0000
Subject: [PATCH 028/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 732f76438..84d680d91 100644
--- a/docs/en/docs/release-notes.md
+++ b/docs/en/docs/release-notes.md
@@ -26,6 +26,7 @@ hide:
### Translations
+* 🌐 Add Ukrainian translation for `docs/uk/docs/tutorial/header-params.md`. PR [#13381](https://github.com/fastapi/fastapi/pull/13381) by [@valentinDruzhinin](https://github.com/valentinDruzhinin).
* 🌐 Add Ukrainian translation for `docs/uk/docs/tutorial/request-files.md`. PR [#13395](https://github.com/fastapi/fastapi/pull/13395) by [@valentinDruzhinin](https://github.com/valentinDruzhinin).
* 🌐 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).
From ccc7c8fef9eaab9d74ad142c50847be7e38e250a Mon Sep 17 00:00:00 2001
From: Arthur Rio
Date: Thu, 27 Feb 2025 05:29:20 -0700
Subject: [PATCH 029/352] =?UTF-8?q?=F0=9F=90=9B=20Ensure=20that=20`HTTPDig?=
=?UTF-8?q?est`=20only=20raises=20an=20exception=20when=20`auto=5Ferror=20?=
=?UTF-8?q?is=20True`=20(#2939)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: svlandeg
---
fastapi/security/http.py | 11 +++++++----
tests/test_security_http_digest_optional.py | 4 ++--
2 files changed, 9 insertions(+), 6 deletions(-)
diff --git a/fastapi/security/http.py b/fastapi/security/http.py
index e06f3d66d..9ab2df3c9 100644
--- a/fastapi/security/http.py
+++ b/fastapi/security/http.py
@@ -413,8 +413,11 @@ class HTTPDigest(HTTPBase):
else:
return None
if scheme.lower() != "digest":
- raise HTTPException(
- status_code=HTTP_403_FORBIDDEN,
- detail="Invalid authentication credentials",
- )
+ if self.auto_error:
+ raise HTTPException(
+ status_code=HTTP_403_FORBIDDEN,
+ detail="Invalid authentication credentials",
+ )
+ else:
+ return None
return HTTPAuthorizationCredentials(scheme=scheme, credentials=credentials)
diff --git a/tests/test_security_http_digest_optional.py b/tests/test_security_http_digest_optional.py
index 1e6eb8bd7..0d66f9c72 100644
--- a/tests/test_security_http_digest_optional.py
+++ b/tests/test_security_http_digest_optional.py
@@ -37,8 +37,8 @@ def test_security_http_digest_incorrect_scheme_credentials():
response = client.get(
"/users/me", headers={"Authorization": "Other invalidauthorization"}
)
- assert response.status_code == 403, response.text
- assert response.json() == {"detail": "Invalid authentication credentials"}
+ assert response.status_code == 200, response.text
+ assert response.json() == {"msg": "Create an account first"}
def test_openapi_schema():
From d974fbdda06c606ee49c3d440d61a4018d249383 Mon Sep 17 00:00:00 2001
From: github-actions
Date: Thu, 27 Feb 2025 12:29:47 +0000
Subject: [PATCH 030/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 | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md
index 84d680d91..8754f5228 100644
--- a/docs/en/docs/release-notes.md
+++ b/docs/en/docs/release-notes.md
@@ -7,6 +7,10 @@ hide:
## Latest Changes
+### Fixes
+
+* 🐛 Ensure that `HTTPDigest` only raises an exception when `auto_error is True`. PR [#2939](https://github.com/fastapi/fastapi/pull/2939) by [@arthurio](https://github.com/arthurio).
+
### Refactors
* ✅ Simplify tests for `query_params_str_validations`. PR [#13218](https://github.com/fastapi/fastapi/pull/13218) by [@alv2017](https://github.com/alv2017).
From 26f27982ac6cf1930ae2db223a2dd35a7a8a9349 Mon Sep 17 00:00:00 2001
From: Joakim Nordling
Date: Thu, 27 Feb 2025 15:06:27 +0200
Subject: [PATCH 031/352] =?UTF-8?q?=F0=9F=91=B7=20Use=20`wrangler-action`?=
=?UTF-8?q?=20v3=20(#13415)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/workflows/deploy-docs.yml | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml
index 0b3096143..aec327f48 100644
--- a/.github/workflows/deploy-docs.yml
+++ b/.github/workflows/deploy-docs.yml
@@ -62,10 +62,7 @@ jobs:
env:
PROJECT_NAME: fastapitiangolo
BRANCH: ${{ ( github.event.workflow_run.head_repository.full_name == github.repository && github.event.workflow_run.head_branch == 'master' && 'main' ) || ( github.event.workflow_run.head_sha ) }}
- # TODO: Use v3 when it's fixed, probably in v3.11
- # https://github.com/cloudflare/wrangler-action/issues/307
- uses: cloudflare/wrangler-action@v3.14
- # uses: cloudflare/wrangler-action@v3
+ uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
From 63208321785d6d11b72f4819fd7d154f14676d3a Mon Sep 17 00:00:00 2001
From: github-actions
Date: Thu, 27 Feb 2025 13:06:50 +0000
Subject: [PATCH 032/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 8754f5228..de2062798 100644
--- a/docs/en/docs/release-notes.md
+++ b/docs/en/docs/release-notes.md
@@ -52,6 +52,7 @@ hide:
### Internal
+* 👷 Use `wrangler-action` v3. PR [#13415](https://github.com/fastapi/fastapi/pull/13415) by [@joakimnordling](https://github.com/joakimnordling).
* 🔧 Update sponsors: add CodeRabbit. PR [#13402](https://github.com/fastapi/fastapi/pull/13402) by [@tiangolo](https://github.com/tiangolo).
* 🔧 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).
From 7710a3480003518f48dc86cf29e8f19b5a4e5e46 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?=
Date: Thu, 27 Feb 2025 15:39:48 +0100
Subject: [PATCH 033/352] =?UTF-8?q?=F0=9F=8D=B1=20Update=20sponsors:=20Cod?=
=?UTF-8?q?eRabbit=20logo=20(#13424)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/en/docs/img/sponsors/coderabbit.png | Bin 20695 -> 21167 bytes
1 file changed, 0 insertions(+), 0 deletions(-)
diff --git a/docs/en/docs/img/sponsors/coderabbit.png b/docs/en/docs/img/sponsors/coderabbit.png
index 74c25e88466fa1b5251f7136bb86103cf79e874f..1fb74569be3efae62ed0eb5608867b9d86e230b2 100644
GIT binary patch
literal 21167
zcmZ@=V_cXOOx4z_6an2%}n-w1Fba(R;KfKzlF`o*#w2Fl%Y9ur?QS{>Oa
z#CZpJE<55DRO9ni?>bA%Bp`1>{OOVL_NMEw>
z+Z~8GOwkUl^L-Ef?El}c1
zFNc7Lc)U~#DI6~SYQ51x-TnC<@BMmCxlmOB;eh@AaBQ~Sld9`V76gmBVwkO3p?3Sa
znJbS5HQl(;>1Ds^;})Nq+T7Qa$zf+Wl~<9*W_4kkZilTp&6xbqLU$a=Cgr-8mz*f-
zx-J%HD=d?5nK6VM=gO}KidroV9pMC9fz6w($E)?sYr!hRxmoN8(
z8ttyc!f*s^mP3gO{D0}2*M@QRy|L##b|MZrccXv0wGoiGZ+&F>zDRZ4jAXW(e1M&=
zG;R-3pql0Sa4R?4>K0*D>URgEvm7#+j^WDY@;f!h+#8iC6~fet*^H*_tV3)CKu(tE
zwzDTES+d(Pm|m0c;swnshS`FlO^CPAVs*@d}$JWonIFLvidOoX5{SS^NP
zD)6S{@S*Pifh8?%X-V7J`NjWck5roLJ0m~X`F0cK{(AnYsHXOyKi+NF_wsffw>Vom
zX~>0a!RJZTnFI*U`}8Zv=i#ebrN(fJ!*025JI?8%UC*r!;2t8W4B6#sRZI(J
zz=>SKiM@C0?
zZ$=pl%F1B;-cPS`y|~*$W{z18v)!n}NH-FJ#bz?>ZKl(1+BwWoi++If2h3g?SV}jo
zYOO|L5?^=DR6S4RC|t&vI@_hW^j{ZuY1F|J|2ZpH(0xG+3_NuheNJ@PB*b<#sm5MeI4=
z>}IU){y=d$7>jB|6fq`bvuv65&$gH~t#LhXBVMqWDMEietLO0WkW#DG>vP+bTQ`{|
z80+0A9_3f5P#bD*$KwlhlYzKT*V4zCf7a(RU+jF+pdM1!>U4P90~yEUdZx~$WqLHPzPS_e
zEhlP1yTN=Sijg0EnIagC$YXDt8SL?5J(19^BNT`i7tXO@i#kr2Mg!jQ^_691;j4C^
z6cWVq-%3WT*IFWgK@68ERbMg@Fs94?FdmZ2a$SkwjK$R5Y_j1|5O{}(6ZlXW8@;~j
z>oZ?#QF8WOHd(Cw)lUNiX#9~`Ay}6v(5A;>ClE;p+nu{VkbpOzZkHsAe$F|!TS
z9!%=@B&+L9a%{>*&hs4?85sr8_&xQ_zuo3O9zZdsg%~_tbu$5Dtqq&K2t9;i6nuTK@p#%c)Nx%7;W;birPu2SuI|b}
zYhS1_d_Q_BN{rpj{d`+x2y!!H{q=W~>*a`oW{C{$h0O_Q-MQw)Z`_t>2ry-1B^e
z(en=s^ai!;{QCHKY^T8k`3^ToaN4gnRrx8m>vguxR;auEOTR!c9ZsU2O=mrB&nl2-
z@O;{iZ!n*Z-i_tSa6MnS`gkS%EW1YKyUR;!NtG{cecXU`1YHT`wY=S&(tugNJ}84t
zaPG3gARTqf>^_{jAw=QOZv)X1D*AnED+qPTFms_wTTN>Lcp9kVCSghB@)ewr!?sAR
zN~^{8z>tL39j)GMa<_hR;9^GSkA1&2f@&0y&>dH8IL_4s8!Tom!}breoM7~Q*vJkV
zhe}Y0SI%qRl_c)-^}B!Kv7OxK@(%Jzc|r9EQ04ssOjy?KM$P7;kvV673(&Ym7+?Ece!*%hap
zVHC?1=rqAa(IlzkecdAsj&!vhX7n#iFLiY$0XfKIDi6Gt3-}I9&-Xb-TI?E|R&A>J
z^50si0LR?X3t+T68(yE_J-=10eCPPyRaPj#7`_i}T4c**va|g9yW8=6aFj*vIG!PP
zcQS8ON^9?NvgTpuPx}6!Kl*>l*p>gCwUEO1cAOGejOnZs@p@A^Bc0NQhUiSYuIwsp
z8L&d04bRhcB477OIH>d0=3>oyss(qpoNgI<<%6Qlr+7
z7j1Hbu`I5zMyo}K*K@zhhVGlYUPMNxNxnyk!=ELxMyCr2X*{J?ixYFdRavw;>Mh37
z7+bbO1tgr(_9R8|4!qT`$?7zYjm;Yb*_h_Z(};{6I@?+v4(f19
z+g_u#`o1WaU9Y(amYNl60v{iKC@U01?z;nWa~0~%Gj)H0>TNbF!$H)
zn}$}Gx5*jrSm;YF*Wf-2h$cz{$QnLp9eH26dkbn^Z93#7lciwy-*<+Mf0cTE5!BQB
zad;2Rb$(-nGD3M}W%R$h_6A4PI*brzIF7GBLM~Kq3JM7&W>3HyjAuk%6yKk6pg{?Z
zF(v2bYM+em=iI#ek7sfW`j(E=?fF_XNPu-157$Y9^Ljst02$Y^Id-wlMdqUY5|q}`
zS>ZcjodJZVC6D(P7p{{se($I4w5#`7N9uouA4UabWH*cj;V^Tq%tD!>e}P&8q|DN`>6G`n>*twX0C6v-)h86GHDbPEk;!D_PGkL_=K{v~tL}W+*UAH%kG&bU{SB0j3r(edX;HW3y
z{dp(Kq4qp(g1>z;PFW5Hxssp@UrmYh;W6(x{0r4#DCc;*?uyY-|Eh&rqi*tK=~~zG7rfNA_lHc2MG!K8
z?n>3FGLVRtx|KI?BC#u;K3*;218CzV|A8=v!|ca8<6-4C=jAZ3%g)45WEP;EpM!sQgcR-x#s<{oaw|_+O02MyQL6+%9wOE_6MDmv1NI
zc7SvO6V3S?=f~Td?Pf}s4HTYx3)*&-4eyYsycqayBz|wywypc2Wn(V7fEDQf=~TgA
z_xlv#ectQijNL|qC20}3?6_5)XcIy^OUSn$P5A`?6nNVzmj7)~pW~kD-Cxm?#qoYvO%ag5-g!R*nRa+R(UWBCdeS;0!xp|86!_3DdIV`V=VaQp*@SJb-t5zA
zc6u)Ql(3i`ER2f~Fz@KgX59G{@AI@br|SC}r9%1w$wAX%=rE^%kE%i7ajk3!gV)}`
zFJTy}G}~1f3$L5vhL_O{>@5TrWr&)}WcFy9RLy?*4a@W6St-lg(GPIa#w@ix0Iqbr
zKX_$%96TpO+;4|TI1YbZWU`of#2#r}@CFhoL)(!wfXT`=(v>6s)f+yK@AbV)01#sF
zeu4vPPJN!NPoP{KZ9PQ!o%b1H6i50=4-`}mvdt4>%d;qf{)T-!3}y1!+>S5D=`qm%
z91oEJPv?(mUgJUiWS8+)EiOc9%o&%Ip7!;1EkFE5TSkj-@b-s#^?^%X8_fMDDb
zR+{74CA`zO(s+u;eW!QPc6!;)n);t@$F@JRFkTj`
zR(pWS_f1OEE0wodX|ND$v<$(*wKLi1AYE#(xRqaR1<3%hpiI3Q1*kJk7b>}ml&KSj
zMaL3;H~w~Zs03;;pt>*dytf39m=RE{B**HO%AYThx0=W}K##f?iX$7n_-SftPS8~X
zuw=T<#3}HL0dV(%ur8o46)#2@omth;fHmkqqj8{Kr4^$bnZXuyG?h>M5BPy}#ydO&
zR5Y;qHmNfK=oNMOiiLwS1>ol2sm$hvOzHSMokewX55QbMH5!6H&w!F
zXz;kjX|kmk0@H$Stidywo2ZqNm$pT{D8
z{Xu@i`&PH>WyJsKAhUWm%V}arQ=PgWnPY84f^^^YSw#u^Tpk0b2q!sNeU2MO3tfm?
zJu^}gXWGT7X|Or@{j8o5K%FRFm+JvQ59#J+)b1?77M_axV1TgNWUb}8QoA{Tv72b}
zZ^Y(wg*xiZEej|dm_LWpD!1D|umTJo`H`-S`xvma?Olc4xtXpWvEof^yy*R^I%2iDnueZ{le$4R`kI{)I-A-4cw4l}AeZtt
z$?mk&Ox5YQQeAXLHz?)!_v$R$lw(1&n4f>3ZbwU|PAD8j6hw2mgBqn3>FSiy?u*6w
z%o>?}jC(Tq)DARKD{|XyLC>Z@C)H0Z>D@ZydWr|qp}4qUO~~Xu+%Zn?*|lAo$JDMp
z!kI{NfmHXI9A^QSr-f~n8`4^LyCO0-ipaInJu}jvWG0uaSu*Fi8KI|Jkp@*(4ki9_
z@Tiuj{M$&bxi;h8SQ~HDuqD>-K!{)lxAms^Jv{dI&9iS@s~Ln%Rh7D;%XD;
z!Mx7yt9+=YI=H*SEW>eSat5hb|Ex}3c{rlhK6qMdN}<5cA`7=$$gs+f!4br|s8N{}
zY;6^Y?eN*;Q+_IWV^2nbx}iqV6Qoj1HCbv@@AzF72vd*3l!*lR;BZJa<3;3Bf@b5v
zwUML>eNZk9*d^I-9i3PjV@}8L5^LJu;ZfZWH4tX8Mm2A(T>tG)>|(~~BZh@XtqP#@
zIPj}<{T4TMCtt2#-~vlpH}aTfr;;+yF4W}$Sx7Joj^O3~P&=0G5qrpvs&qv+pqR+m
zew!vYzzsbUBh$7V`Y`PcVQcb_(0Md0L+J7H7rq)mrs1MJu$D+!!r_x#7Ob#e&rx@z
z8)+n~(CX*jKg`^%*QN4kj|B}`F
z6^~sOvFK?2bveb87&{SYw>em?*)^uG6F#l1W?{ho+v?fx(7Q_m_8CWwbN#cH>CgfC
zy2-oKJ{bfy#hK>@b%2o7z$+2E&H63DNq^{iN_EBA466#oVgxASF0p@SSF+b73U
zGi?7)31eS8^xd{=gyaB@371_3BabW7lSt_k(`d?NlLt?fDO1qiP6H2wR1AsHivYORA_
zN3nrk#K51f7&;%}8?2VediRk3SFbF4<{B3kqM0!%?jMjeHZ{nuw%b+IA!svMwAiz)
zBJx!9sXwK+m-+=E%(1~Ik52vHktSJQswcviSJo;hWb5RY!l06%En1$^YwK`jV~oDS
z)WhKO1k4afASBIj_A>k>AZj<~7S1b!xcUrsY>RZ%NN3q`bhqU%o4LXBub
z8s6cZVQR?e8V2G)=aYfDR>4LuqJ)Yv;SRqHg{b}$ubj_Q@lR57>-V2U?LmJ1TP8vd
z(cLPc;QynUwDqB3;;4hnK$k4qo!I87;M&>)8+IcmY23qT$BC;?#e;z$AR+q5YUJF7
znqJX_k|fBZ?Z=Ka+)0^XC9;92BJGa2U@dCX_Q8yOisBiLW@4j@(Y`^>g`yz^#RUSj
zl8`@=n6sedU@W%dRLx~Hn^ailAMHHT({_gm=?3@VD?D-r+G0^y9E}Gx>3=~4@iGRawgcrh-ibq!zqbwni#~$%E28k&cKX8(5MZCZ-
zYl}BGopz|f805F4B@biXCnIkt?lmR{pZ-PSB?}k1z(2W_E+2aj75v(#THoCnt@7&+
zh$WPROb~X&UQC#IX>7VSD$M8)Z57I;BsydUmgyj3<;L&i-sB%Jrh^*>aLR)dgSGd_
zr5tA+og@b{K#e}Z`fWgcER(Yg%
zqZ>K>vn)7qAgyeA61-E6hTbCjognO;JKp98gmc-c_ztHi5#M53{5GwX;qXy4x0=2-
zg~pPaQX;ly;{iHlr&9O+%7lz6A{q)
zp27Q_wEQ0fE1jl&m<~HsE*73hyz;_T$h-_oyhi?0MbfklAGKtu4-uJt@N~9F5LOCg
z#g{LW$z_7uumRxp3vgAH`O=yf)sbP%hWk=90$cCj_k+CjO0#*vjGG
zSKcuwD&k=%RZBDeG&*R)XjZh2($p+F6jN=Qn99dkv}#p<9Eg5QE73q$nHOQGbvOr)
z&Db$#Y2sa+aQ;A?%K(Jr)XiE#9|+i{LW*VbG})U{lo$5v~uw9j+$dEwUCx|eWU$boIUR5OQ64yXwJPYV#Q
zuJ02Cw6P+8ygFXuMc21#I9q?15Ehd7Ug@ngTlGy3=Ej#(L>CxwRzN%9_=jMzR_e!<
zk`Ulz?xyVg+ITlY
zrM1P^Qp%l2SahH>n`QPZ2iG*{|8=D^*zhy*KW)omd$jmJ(iLD9a=IYs5;X*l-&SA2
zO$HELj3kbtC5a=~nyP}7lz0s*2efD*y!fWTXCh~%q2o#DrYM7{YVzbc$-=9?+t@&P
zdH-x*mxG3e=6yXYBy!b26_5My2S~)2;l%Dzr$hNHzmH`)jhb*$UyT+SAD^o~r!AmS
zT>a=#O@4zOO@MASW4B%!0;tYw7;@$zyDApDfvrX4FVbG
z$5LV0&r?Br7HI!kTeN
z<8{JO=EnD#YjWeot$d~O3K%+=5te4R-sp{FvwAi?L1O8mpzwzu6!NrXRX|Tc3~<&J
ze*ACOL)^_Jdu&a2(Vr$B3|xRtSAIQ%%|fS}q)>ox@J&o6t=$|RF_odz_|=t4K705^
z)yiIX(f%8?LvW7_rF~J*sU#7#gD&o1ygMk0cHHdgP||{~4GtqVh=Q1iTB?O1CrQ$4
z-HC{ZOtJ+9L$VEook@J7gB#%>A_eY|6Ll!5L+udqH!-1z;+iOeg3=>aL>Rc3l8m@r
z1CEGn$muJ}fB#`jG7EV2#=9$TEklbzNJ%VR$Ly~JjFH#NC_FHx!5|Yz0&G_6aS2(+
zFM{B`FVWEA^M3~;=L08I^a#NfaHOt`;f
z6c;%UX+D)l@=u_U_)X!TJfg75e$#!Im`XltO#j1?5a8A^7`v_Y8rI8{AOU)XWBxbp
z53>pHF1%M&EG9B2R;z_1iWj0#XCnTOy<77vYen^?>g`#^!Ft#A7jrVW
zRz36LciR;);c<
zz0*to(3O8=Rwevo=R4`!7qDV`-BcGscIA{CDayn-`rm)_Y_yrZHc-ztTQS)xuz%Pr
z=%g@cPjq>?<=Kql(CIcsm(QA!C!y!+9#54Ud1{l0$9C7NeChZv{a)P=&WDEzS6_s@T
z9*4RZ*>2}6>Hqqyhzu>{5C{4nKY|tbpR<4-y~&Yc{hzl0*o#Tu*Yi<8TR;eTg)VV7
z01&8v_yOTnqx*K^wF5ZNpFAHghJ7B9`yV0MZB|4RsbcU249V4XeJIKs6edZ+n7o(P
zi+)-FU6|2++?-4g@K!WQYJA_xbD|nQ^#O$}**=*2*0-Gio!7zDbZ(;*W}*;six5H7
zKIBx8mR$P+y@O6_QRW_L^SW&PKuj8|4OO?*bkgl>c5kEGJMrdY>-~s~`%8?Q$3@j6
z8U%>G2aXzl?$Y>$_?CxiB27&1N1V{R{Pv~7XQsk{
z!rmOk>lQ3O2NGdGQUY13zNnBd$@?c$@Ok+Um@IFib8Ys+sLK*^;%`5CLTo9hk;7bd
z29NGiWb|Dx(*Fh6b_rggQk3(SgI*_%CP%i$NX!3HQ;RMm27g$Lus)ow!TXE$?yf)b
z?{t28i|Y06?nF%#x}2(w<#Ob5yI4B)N)0kQ{@tzAteqj~7PJ)e&B1lx!#}Cj`k5w)
z_v*hui7|jp)i{tC*x_9$U~~=+0wV9<#Q&t9DXTut5Oa$hAc9Z?33qb@cVEHb%axQh
zDxAxHnfHW09mxG4IF?j~634~$oa`h#_7ekiHAce;q@Onz)){~X1(wL36Rf)PfgO0`
z9@6=nkhKgpEj9Tv9O#aUgl+T!eL=39vY(s}Exv14VcJ}KTaZVKmsmuDhC@{xb&mZs
z@|^qD@;tW?F#1j`F}=7A@P8b-1rquI84Vi;%JzWv<-yx%wb8*@>zez$3EZ)9v`W4<
z9M~zQi|)huQozOfgVDg+^Y^^X>%pmf7a4~3ds|!5YrLQjZ`{mgET?(@dW}Y3NX(Ln
zt+bplg6uY1-L{MQoN1#${CF-THfN%hX6p&fLE>={#?FZ<)R?`{Z3;ZT2P@FoW@<<9
z;L`BVrFJ=5gqvF~``k|I-+IUbig*0>0z`{OpPi38r_{6Yu|Y;$Z4J4xyW)&um~c9b
z0lS`@u`=d}u0_#;{9YfzpqE?yr_~?|2i-6nxlH@r|GFIk=D#--Ig@iQ&lYQMfA)PU
zXbJEich9~0j;FKlB=3WTm3p<2vy&%Lhb>r?i7hi^$5fInBp~bx#12Ym_}f%xOf0HI
zN}^QnMT*Vq@3NGV+zjv*9|^HC%G{L1sg@CxBq(gRH$!eZH@c}O5m4~Q%3Gd$QmMj=
z6x75Bd~w2){g@~rAQ@q447IxnH%jPON^0om*e@1hrw|hV9m4Tm6K?eY5~9khkIc_;
z&I4TmKq+SMe1o05V6~Y~%w~miy>e>zQ&|~8_zI{?Y904!Q25ITjk<{x-aTDBuJZkA
zEpAQG0?TJjnM_WQGC1ra1*c4MJC&rJCelkCfg=!Yu39}cWjr$>H&?=fhWI(C*r#;L
z!$)DeqbvP!&33ILWLtq3QFzLu`k}}tmv-VD7e5jK@upn8lzryb{WWp&dGr(6bhC4%
zR)4Tl@|FM6kLwRXvvrvAOu3IX7|Q8F2;{>uA{k|)l}f3W5Py{xt5NURvd+q-ahtRC
zBy$+aP|Ia$t?jI-@8+!{4oE{RrWazsM}U}&^2fH2!A@rKr9=gQi<5D>G$Fz7k7nTv=Phsil3!=
z#0a-H=vswUwkMKPLV~>x|FJ_z!v4O)oXgrXWtm^#=D
zLJIA_{~*NtDH^xiV#6rdJYq%7Lxl5^;;;zldmJbO5Gp^lg8qBb+U3spGNsgzg4PXA
z`~;}Nc7ZUuQ`6VR0tj`cU8ui4Zhoo*{nl}dQj?9X9LY3}+ICyhegK`Yn9?ok`(fki
z*fRimIHGgt?5YoSw`tY(*Wd8L!bR@|fLNz-(@pK|6>hRwmA9~PAFROeLQ_vA8L>zxGohuT0#3V;!TwKY_^0gSMDt*!I@?tfOs-S$7$A2<8MX`A%*%(
zyHU64cxa3@4YlE)n`9!ND{!JU3p-nnBYo?YrkE~3LPZ5PLOFJ?9A51*Hr%%!Obp^DNeMzLw!kmhRNG(C0?^BK
zQ56GX;G8P8M>rNPdpHpB5xzowr@$zT+1KNS2q1@xwxTDK{Ssase7{Q=bziL)?E7|I
zs#*K%YhT!B2yJyouwJL9{oAz!i{oApk%K6sc&vA5UEL2HM)qG1&xg6=;ILM?R&Nb|%Pmx|zTo|M65#xJDm*I7pEJ?wZ@T}Vu`NfT`gYI;Hr23WyO1c&4)3q^IP@iM%^=w{{?U^Tlvt~s!VqWjYp%#eH3A7d+FB%|qQ(Iy9i71r
z(*+kDqV28a+u*}^dmV&?XQe!enQILuGjL%zpjhosc#hI+wxsuRTMj#@%1A_Gc-A9%
zwhi>=2#6AyxxTW$3Kq%P-6o^Hy7_*0GnANvUe(E)5G&D@NjWL?UlNIjZJ_0__f3&R
z7j}1|e4saDT(JFcz0sbR_}zpPCS{Sx%)O*!81H0XB8CFDyCjhndg_S%74M=X{IH}q
zd%=Bm4y4^mL%P#sXRyHAwk1QGIT*-A70?hy1C&_^ssO9qw=6Cv_Uf(|9G};mvHUQS
zzh0L<8r!<sAd#O_zl$<
zQjqU1G%mi!^Voh;Jifm(^mJI>OyocQ*v^#t0@b|np#I_cShePR8UrT#^uW`V!xZsw
zu9ew3ku#X>*Pi=H=nW6~@(8`PdRcO_#KM>nwn2^iiG(hZ({9cz@^KCKxMt7R_i^W^
z!*_S-&2{ZXLVVASo8?L#Giv!kDgVZYm`Z1KF|#kecBeyF
zWSD@=ZbQEf`j&H;Q7^x{>9&c`)4NaGdik6RPiIBS;Q|90D-aqtpg;)+K{UDBca3?x
zR0q9VIw{_!hXu9WLZWf4sb!Qh(1EH&8x2SQLkdBPF~Ugb;>(Z|W_pVWnN(ZEP=7E?
z`B&Q1%AH*|{`|vuKF)^6eDf;JPGjiV?krhD^4u@F;yI~c#Y3Z$6q2R(Yfw46TL|zo
zY`!|1gE9v}2xucv+U5c_LHx4zVGpxP4XHr`3dv;X?h!@s7hsAr;;ztTf*3`!EwN#0
z{IQvW&=yHn5UMI$Khy3M0~GSS6WFtrX+sVXAoSnZA-IG`CMQd!62-08wR%tHt#cmWmIcH2PPNz1ed2DG_`BHSSJcD%4+wLCr8g7s2(
z-5(-zOn$hn7SQ;%3zMeI$Wf~n;bUYH`wxUd2~WL&ZUjkTx7di^j>UhSh>Bnq9lpRx
za)RLfuOM$Q;EBFuOK)=xG}AT=)OYE_&sAy?chiaeHYQAvO-J!23V-VcC6zhTSdzk+
z8W!T4XEIk^#p53D;~3gu)b?cz;?l-T+gauJWLA_R6!EuS1}Nw
zJ@I$t_PPkdT4bJ-PjQ1)MW@mG8s#SmeA|bxcjk-w841_JuEvE_0@p`bp8!|1YaB89
zg=o=uk#oR@5{+tsvmFH8vinsQ$|R`nomcGD3^9?4
zhUmyw6m~rrepik){1n2P-iJFA&O;jkLnZb{5QGgLMiz#BD*pjF)guuORs{(m5JQ1Y
zbEPyQ0o?^%+go&7q2CSneoBe%lQF3G7i4CqZS+=5CW)oR!yY^(`b;~z-xJEdQe~xr
zw&X&w3p2BFrWTJ~n--yjf{EXoW{iGqT4Z#HYuwMi+Le?m?x|-g1Yx8)fxs<->0q7|
zTLeAmFfHW2+cKbA0-lZZ_*IiG2?d^=I|`
z9Rb6FXr-tY8Z>~7K~MZjOfaqaI-ur{1DCIO+@M6ti<+d62QfvJHseAZD7ejqK~D*e
zVG`r2ESTpDsdl6fD<zSwtp1dG4+*az(i@iY)7
z?`;DzdBBfc44+Uo{i_ZO@9Tf)!6{6zt_)Xk=-DEy0jjuKgA%sT8N|y|;g;*lWc4hE
zu7-oam|zgoGTK|K#(r
zByH|&tV>dNMOQIZravQ{Lm-1B3RpFW4?q%FHor@-zT4cBruUa48&IgHuqz2_QA2Dt
z7yOPEBzWQv$ejR_GC3(^Tj2o3e#sVpHl?e=qG4JDO5f1R|JY8y4ADWXv`93(BenVs
zB{U+yjf_M;*1`=+C_oi_sxe;hb7;9e}7
z3$l+6r>hdvl*EXY0ali_2z~(#10Se^(F53bT4s(^2+CQNEim*TBrKe1Lgon2q1@T0
z`jRm$oPxem?I}S8?2oL3&w{M(JZ+*Kp1PQQHThQYx@GAaAj5lJ;?6rmU3$^v_Gx+5
zcO>nLkMW`m-_6TDUWLlPPLy6356IN=HVp}JxbQ9_zE_+$P6^m?s
z!!YtlTT_;3nz4ID(7+D
z|BdtV%r|SXoAEHmg2A#;niS?MEm@IM`te6_4bzZ_)+qwtPIy
z9Qrqo*KX0R&dbmW`{1p1uG>PD7C7(if;<&gH*BaTl(()|A*9YT;RoCG1jf>F(PHI1
z*3<{S2&>5p86iCq7C()oWol)M3O9m_Q!W$?q1n*;=RRz+S&M2l@3&zn4A5vEqWr;D
z&||*tD~|O_O-Lgx2f_L>1CEE9pGyO{mG2MKWOi<=UKjWoi$vW$PIfs(@+2vdyP_}O
z>DiX0!@vI#LO=_M2z4wHELIj@9Ehxy78{_hH-~Df8jzui-rFjTSI(QIhGeI%Vjjb5
zKq|u?Q6LOTsr4lb9`V=A&rJMD6P+1MX)=}Blm;avvjDWRla;|(FE7tO3g)3yO$ps5R(E)E(&+3d!oL96es#85PLWtLVX%8J4yQhouZoCr
zvXES-k`XuHD^`kPv)t5~L|uOvfaZL@lmp7
z-AqQj)Z&W?`fuUN_-$x=fdl0HpO$7}=5?-R;-(1N6MK+E9%SMLZc_0%Lp+SVa-bqR
z0}tGpdnKi=!NqTtd@$o69C7TSL4s$*?zm8bQ}-oGwoqEU1kV5;QLN*E_N7u-g;YN)w)x%
zx>RF|PQDYU$6_
z1th-{=t3*a_jcEN%QeU2=eN^j4geZL50bckmv9jXN6u_k{4Z5P>E1HSRn(3F&ynNn
zYE#fXHX>~XyPa`eId_w}cprD85S%3r&JH)qs?6xWZmW$ptI^SXs^WTRdCpQPY*cD>
zkyxU5G(JcCG0VYpfhGkQAmJbX1M6HMlj;h_YOWDx3+wu8!R;0;7P@Ky%9mi#K}je>
z5WjXqhDt80c3fXPt&E!ft5(LjBPGN(+|}$+kNs$#GofBDqzx{Zi7s#2!weN8UQh>k
z_QOx{J5{PLeFRWrO?)6ty5Ol2y0HAGx5m!C&*uX2PvcoSOhQ4q&2_
z|0(5xHR<`w+zkkAu}|dEe`F`qs^=%|tlE9Uv@Kg~v@$D7;(dJk9@yi*lYXcN;M~g1
zaBCMXUT^EY`aAv`tQR6Dg2i>tU>13vctTKX?@>$s0TC2FI>IiB<#Y>r&gsntzzIti
zPf1^2AL`c`?owG}QC`M9L$G!{FF_*lU&=PwZQ2T2Rt+DJ4J+cIz+a`$M|5X%6Z>02
zKa1zeza9yq7b!PbShpPf<1ZiEFK>6+Nv~$SNNe5uX}}H`bYztb!Oyi=DY73tj#||<
zTHL9tV?HPyL+G{I651Lghh(UhfE{~Q3zbIW87{zv%y8w%ODcJ@@sY*>xsn+EIJKl?
zo6^ckET)U5FIUtuHs9cs@lwls6jITPenR<1e$fp@@P1Rl7a9rwtBjD83xQ>_+9dwV
z=065Dof(Ay>7WNC3ryy(!4S#f(A0r57*qU5tlW|T57WNXTNK6Or>r!(p%0^Dr4Q4t
z3|htX=5nTqxzhQ}N?KaaDYrC`w%`GF9AE6C-F5o|MwO(7*?|O3jObdXS#KKRDQ!Y>
zgY*R&*k#6H6B$`>Q$R3JTf~s!=b)ykiLV_Q1#C#@Pt~utewKdNtY$Hn+ky?9H4j0N
zJ&@zP3Y9bc7Pmm3fge?m#cH|a4_sT7FU#z7;Vn<<%(nh;{>pEQAFgX2@vghJ=Qv>^PcSeH%76bCfGXIa
zJN6trt=S$vf=Np@txDMknVw(~pz>(4O|N%=)aeTb7L{5l2n`-XxF`nqr>&8YdR@FOL3*6cAHWEBkO`6aA
zw!S3FmJk9aO=0r^%b!|S!BbG>SXwLUn`J#z3~<3Q9_+DTqz%KUu0*dixK^4A0rN4;
znX?w>nb5MZN*6V|mapps>Iq3Bzi9^S%c>MO8>rR5TZlS=-K=Zgw=QqT3X8Wdr*^Yl
zKH3vphtay$Rrov)R_t~gz2hAEVV9-U%T3v|u&5)yF-(b;bYssgPSxm-X4_$vr5}zP
zaBZ}j_p%@PMXkmQzRGYN`M+~MM$e{o8=xQF@yxpxYt|W4>&vD6>7ZmKmrqK{<&1z6hLU`C2%gGvjXMwM0G_g
zNmf+kLkqdpiijiTS$o>nV7mb2zkG{Z<2Y}r2GW!$d~ta`l~ZT7+s0^fTgCP7mr++%
zQxo3}bpg|8F~>JCDQ30!g{&2o`ctbZJNQ&t?SOF9$>8C?8zM;Md~A~u3Coa7NjzIR
z*)((Ra|&i2nUbHyl2`#u{AxD+*6IjNcB{XB6@?5aXvS>Y(CXHqlU
zUaE3*5!|9ZeZ>9(nYc%W^d&}nqIq?c=6?ba5bp02jfDcC4CsX-Rhq_eZHQ%{Y_NMo;L}&11z_aAUt!s@6((HMb)$$YFq}Bt;5-bYjQqf0;;hrYHJNG>u1kx?
zjnTmDc?(Q^A!y&C8J_F1t+bRS%T}P3;;w%@#%JIt&m!^YO!L)N~^!_oI<0H@ytjXH)=p}aS2(+0eES0jaD-a};NsjqlZx$?C!pynzL71&}-+%cT#(eoTci#6;OkA^|Y2!w0
z+`O5s+qM(FE>y_yWtYNYQfJN4#yy7f&dO3kXEKz847ZQ5fhIOW3?06-%=n5O*Tr)j
z8rG}Jh7FrYr)(^=5H>l%Y#HkyaD~KmoG9GTwaM;U`Q*8t<-EqCoUn#0p_SeCRhg|?
zt*<-jNVM`PNO(9}+Z{JFK{`c#B7wvNP9$s=lG%|`+h7k9q>%x%N#k>b{Rsj;+BjaP
z)3~m2tvLcC_NILtC$wLL?jR!)2W4p-7h_#k#_wyy`c5X$VzLI%`++UUVlr4#Oc
z+TrU}LLh|4*?_h{4cmtSAFEyeUX>`>aYY{A9X
z+{nBIi(~VM+Gc?-e`@XY>J
z(!SxqfZV~c9MHk+Y2`_y-&T|x28I9F~fk!8B&FD;w9!PM4wjTqvfuM%izmFLd_XO~-zpYHX
z?989G?t8$>u{m(*X|#44%=;J<_5b>pKjTk-`~!ac@dy0#Uq9h*fBy%5`uBenZMm>w
zjvcYi3hPAl2WIW9u+fW^^nwSIXG8EM2RNS3bDh-kO~fPDaAYBZFce2z8>?R^G_V>0
zT?O1-h1HpiFqxMTSw_$mGBHLM0Li?j#@iLMh+Ki=ODl|SG8Rm42IVDp?5gwJ%$4y}
zauQ%KC9-owU~SDXPTJI#>^UT(C*WE0N|9X_@|bqAStO$j)an@BlacwAxF$^i<`^n7
z`vmYOY~Rx4EL5{}^WAl>zx?cykSM{z$jTXxGPAK?(??DuF;NIuEdE|*(uBqd&$uuT
zcg$W%zO)@*0g7npt+(OGxNIHHWDL_W0@t!V3X|wrJ~rZzS^2_L2diYImOGd+`^{yrB_n11=Y$kUXKgxX&NjH9gNPr
z*P6C7T0ki_8wwfAD-*J%D|i-^Ao_o{`QMq3U7XYB&Vl`dfLW$-$!h1mgQFSI#S;RRs0M9
ztw76{4wFus3++SRc#Ke-jtdOENc0Du6U
zr!9`Acy04uS>YpDS52)gs(D|qbe9T0@OfM2?k+Ox#>{`dEN5Xd9J%t31|#Pap?paY8syP64&>#
zdeMDEF0-k%lh11b3@|{$_0QC~AObgh7>1d`BL}(xs0KH(9%{vKhj%s_E1DGsfFD{x
zamR7cCfy4G!*qH_2PHF%=a&lfF)%DzdNNei2Z23?mXVCP!*2(rbt%AmmyPQ)mc2FR
z0e1yv)M^;raq9hb6(sCN;J3}dkZ&^a;S&>wM&WuLEK-u~sUQV#gqLTC;
z%QYb+ik@u%>8W;Q-MGB%eK=e)x>dvBz>pK`t#@n!t%LS!tDKbH4-lj#XIBBtfuB3D
z$y+I1tN31*#he$8fd_r77;P3XH;isnCqy
zQ!(J+hX8kA#Mdv6^w6;hkqQ$4xc^3aI=ehx1p63|5KnkE3
z#-KfF2AqQ_gKtXeV5x1#0iXj}32uNA*hroPdNmwl;7jHE8c?L8JI*d|b}_u2QI{Fm
zo8jm8fiIh(gP(B~t6QNHtQ-9hXP2D~zMoQo6Tr~-5Jf9t#@8V5S@?t?t_|o8^kHJ~
zCjj#^*e1LPxJR1r`MW1aEArEdw~n<
zt;Y;gRxihwVX4l0I&)S{8{h>3vQnp$;J+v*uLD_NeH{Xr*cl
z&Zq^mOM*$>3OEs{Q6;N)jr~$7mBSfjaJ*Uo^$y67!ot;JF;F8UuBsA6lqo<0Nn$AJse?5S+@cCXUrRy$+
z4w3Ov0k?tEL%PrB9?qz(1CHU8ze69#Dd$wHMyDHS|9Ua>0$4S?pZv!90Ja_Y+9~U;
z@_meA$c{FUxv)PO_`BU^R!{)cmVI*_7?=RFJ6gnrDHG_I31DHx%SBhIZ1GHXyI1$V
z4)p=p{ljORE-|DtE0{T$*CcI=WHp02JY$`MjhoF7%T_#>n+)8y7yiD455
z`<-FYueQ3!_y#4`Q}Rk6knBy%Pnj@9W+8wU5>AFvF2c0C68m8IhblGr&ptoHFO
z!)s?W;kw@HhV|opEo;nk(P~O>4^V*1MYb}z$x|E7uED^IQ`c1kOcM6g`mGg1kyGx!
z2buL0GrvWxf}NuAa^*tXcdZa2JD-mJFqp`wi^_Q#y6#IEKqeLI*fA`<3(_MGIG*wu@Yg<
nLvVL@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?=u6T}Fq1;d3
zcbNmC000`xKJlOfSf%|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<^$
zb7S+F5U7sY?LwaLFkWER8|RnHlA9goU*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
Date: Thu, 27 Feb 2025 14:40:09 +0000
Subject: [PATCH 034/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 de2062798..5366ae437 100644
--- a/docs/en/docs/release-notes.md
+++ b/docs/en/docs/release-notes.md
@@ -19,6 +19,7 @@ hide:
### Docs
+* 🍱 Update sponsors: CodeRabbit logo. PR [#13424](https://github.com/fastapi/fastapi/pull/13424) by [@tiangolo](https://github.com/tiangolo).
* 🩺 Unify the badges across all tutorial translations. PR [#13329](https://github.com/fastapi/fastapi/pull/13329) by [@svlandeg](https://github.com/svlandeg).
* 📝 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).
From 7eabff43de6717a366c79ec92b808da988c085a3 Mon Sep 17 00:00:00 2001
From: alv2017
Date: Thu, 27 Feb 2025 16:42:41 +0200
Subject: [PATCH 035/352] =?UTF-8?q?=E2=9C=85=20Fix=20a=20minor=20bug=20in?=
=?UTF-8?q?=20the=20test=20`tests/test=5Fmodules=5Fsame=5Fname=5Fbody/test?=
=?UTF-8?q?=5Fmain.py`=20(#13411)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../test_modules_same_name_body/test_main.py | 30 ++++++++-----------
1 file changed, 12 insertions(+), 18 deletions(-)
diff --git a/tests/test_modules_same_name_body/test_main.py b/tests/test_modules_same_name_body/test_main.py
index cc165bdca..263d87df2 100644
--- a/tests/test_modules_same_name_body/test_main.py
+++ b/tests/test_modules_same_name_body/test_main.py
@@ -1,3 +1,4 @@
+import pytest
from fastapi.testclient import TestClient
from .app.main import app
@@ -5,29 +6,22 @@ from .app.main import app
client = TestClient(app)
-def test_post_a():
+@pytest.mark.parametrize(
+ "path", ["/a/compute", "/a/compute/", "/b/compute", "/b/compute/"]
+)
+def test_post(path):
data = {"a": 2, "b": "foo"}
- response = client.post("/a/compute", json=data)
+ response = client.post(path, json=data)
assert response.status_code == 200, response.text
- data = response.json()
+ assert data == response.json()
-def test_post_a_invalid():
+@pytest.mark.parametrize(
+ "path", ["/a/compute", "/a/compute/", "/b/compute", "/b/compute/"]
+)
+def test_post_invalid(path):
data = {"a": "bar", "b": "foo"}
- response = client.post("/a/compute", json=data)
- assert response.status_code == 422, response.text
-
-
-def test_post_b():
- data = {"a": 2, "b": "foo"}
- response = client.post("/b/compute/", json=data)
- assert response.status_code == 200, response.text
- data = response.json()
-
-
-def test_post_b_invalid():
- data = {"a": "bar", "b": "foo"}
- response = client.post("/b/compute/", json=data)
+ response = client.post(path, json=data)
assert response.status_code == 422, response.text
From 3639fb00be3acad3618f97c56859e221f7fba4a7 Mon Sep 17 00:00:00 2001
From: github-actions
Date: Thu, 27 Feb 2025 14:43:04 +0000
Subject: [PATCH 036/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 5366ae437..95d0edfe7 100644
--- a/docs/en/docs/release-notes.md
+++ b/docs/en/docs/release-notes.md
@@ -53,6 +53,7 @@ hide:
### Internal
+* ✅ Fix a minor bug in the test `tests/test_modules_same_name_body/test_main.py`. PR [#13411](https://github.com/fastapi/fastapi/pull/13411) by [@alv2017](https://github.com/alv2017).
* 👷 Use `wrangler-action` v3. PR [#13415](https://github.com/fastapi/fastapi/pull/13415) by [@joakimnordling](https://github.com/joakimnordling).
* 🔧 Update sponsors: add CodeRabbit. PR [#13402](https://github.com/fastapi/fastapi/pull/13402) by [@tiangolo](https://github.com/tiangolo).
* 🔧 Update team: Add Ludovico. PR [#13390](https://github.com/fastapi/fastapi/pull/13390) by [@tiangolo](https://github.com/tiangolo).
From d90030c1e242d1e954a097c253734122eec926e3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?=
Date: Thu, 27 Feb 2025 17:40:41 +0100
Subject: [PATCH 037/352] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.11?=
=?UTF-8?q?5.9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/en/docs/release-notes.md | 2 ++
fastapi/__init__.py | 2 +-
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md
index 95d0edfe7..833d52bda 100644
--- a/docs/en/docs/release-notes.md
+++ b/docs/en/docs/release-notes.md
@@ -7,6 +7,8 @@ hide:
## Latest Changes
+## 0.115.9
+
### Fixes
* 🐛 Ensure that `HTTPDigest` only raises an exception when `auto_error is True`. PR [#2939](https://github.com/fastapi/fastapi/pull/2939) by [@arthurio](https://github.com/arthurio).
diff --git a/fastapi/__init__.py b/fastapi/__init__.py
index e3e0200ae..c0b4cb989 100644
--- a/fastapi/__init__.py
+++ b/fastapi/__init__.py
@@ -1,6 +1,6 @@
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
-__version__ = "0.115.8"
+__version__ = "0.115.9"
from starlette import status as status
From cc1f5de03c715d3908d19aa1216f14ba99c4dd6b Mon Sep 17 00:00:00 2001
From: Ben Beasley
Date: Fri, 28 Feb 2025 09:07:47 -0500
Subject: [PATCH 038/352] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Bump=20Starlette?=
=?UTF-8?q?=20to=20allow=20up=20to=200.46.0:=20`>=3D0.40.0,<0.47.0`=20(#13?=
=?UTF-8?q?426)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: Sofie Van Landeghem
---
pyproject.toml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pyproject.toml b/pyproject.toml
index 51d63fd44..1c540e2f6 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -43,7 +43,7 @@ classifiers = [
"Topic :: Internet :: WWW/HTTP",
]
dependencies = [
- "starlette>=0.40.0,<0.46.0",
+ "starlette>=0.40.0,<0.47.0",
"pydantic>=1.7.4,!=1.8,!=1.8.1,!=2.0.0,!=2.0.1,!=2.1.0,<3.0.0",
"typing-extensions>=4.8.0",
]
From b78887f3d3248ec3fad8cd0c93382354096d4f11 Mon Sep 17 00:00:00 2001
From: github-actions
Date: Fri, 28 Feb 2025 14:08:09 +0000
Subject: [PATCH 039/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 | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md
index 833d52bda..1c6bd259e 100644
--- a/docs/en/docs/release-notes.md
+++ b/docs/en/docs/release-notes.md
@@ -7,6 +7,10 @@ hide:
## Latest Changes
+### Upgrades
+
+* ⬆️ Bump Starlette to allow up to 0.46.0: `>=0.40.0,<0.47.0`. PR [#13426](https://github.com/fastapi/fastapi/pull/13426) by [@musicinmybrain](https://github.com/musicinmybrain).
+
## 0.115.9
### Fixes
From 29d3739bcfd26160a4c77a391789feaffa416552 Mon Sep 17 00:00:00 2001
From: Valentyn
Date: Fri, 28 Feb 2025 09:12:19 -0500
Subject: [PATCH 040/352] =?UTF-8?q?=F0=9F=8C=90=20Add=20Ukrainian=20transl?=
=?UTF-8?q?ation=20for=20`docs/uk/docs/tutorial/testing.md`=20(#13371)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: Valentyn Druzhynin
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Rostyslav
Co-authored-by: Sofie Van Landeghem
---
docs/uk/docs/tutorial/testing.md | 240 +++++++++++++++++++++++++++++++
1 file changed, 240 insertions(+)
create mode 100644 docs/uk/docs/tutorial/testing.md
diff --git a/docs/uk/docs/tutorial/testing.md b/docs/uk/docs/tutorial/testing.md
new file mode 100644
index 000000000..25fc370d6
--- /dev/null
+++ b/docs/uk/docs/tutorial/testing.md
@@ -0,0 +1,240 @@
+# Тестування
+
+Тестування **FastAPI** додатків є простим та ефективним завдяки бібліотеці Starlette, яка базується на HTTPX.
+Оскільки HTTPX розроблений на основі Requests, його API є інтуїтивно зрозумілим для тих, хто вже знайомий з Requests.
+
+З його допомогою Ви можете використовувати pytest безпосередньо з **FastAPI**.
+
+## Використання `TestClient`
+
+/// info | Інформація
+
+Щоб використовувати `TestClient`, спочатку встановіть `httpx`.
+
+Переконайтеся, що Ви створили [віртуальне середовище](../virtual-environments.md){.internal-link target=_blank}, активували його, а потім встановили саму бібліотеку, наприклад:
+
+```console
+$ pip install httpx
+```
+
+///
+
+Імпортуйте `TestClient`.
+
+Створіть `TestClient`, передавши йому Ваш застосунок **FastAPI**.
+
+Створюйте функції з іменами, що починаються з `test_` (це стандартна угода для `pytest`).
+
+Використовуйте об'єкт `TestClient` так само як і `httpx`.
+
+Записуйте прості `assert`-вирази зі стандартними виразами Python, які потрібно перевірити (це також стандарт для `pytest`).
+
+{* ../../docs_src/app_testing/tutorial001.py hl[2,12,15:18] *}
+
+
+/// tip | Порада
+
+Зверніть увагу, що тестові функції — це звичайні `def`, а не `async def`.
+
+Виклики клієнта також звичайні, без використання `await`.
+
+Це дозволяє використовувати `pytest` без зайвих ускладнень.
+
+///
+
+/// note | Технічні деталі
+
+Ви також можете використовувати `from starlette.testclient import TestClient`.
+
+**FastAPI** надає той самий `starlette.testclient` під назвою `fastapi.testclient` для зручності розробників, але він безпосередньо походить із Starlette.
+
+///
+
+/// tip | Порада
+
+Якщо Вам потрібно викликати `async`-функції у ваших тестах, окрім відправлення запитів до FastAPI-застосунку (наприклад, асинхронні функції роботи з базою даних), перегляньте [Асинхронні тести](../advanced/async-tests.md){.internal-link target=_blank} у розширеному керівництві.
+
+///
+
+## Розділення тестів
+
+У реальному застосунку Ваші тести, ймовірно, будуть в окремому файлі.
+
+Також Ваш **FastAPI**-застосунок може складатися з кількох файлів або модулів тощо.
+
+### Файл застосунку **FastAPI**
+
+Припустимо, у Вас є структура файлів, описана в розділі [Більші застосунки](bigger-applications.md){.internal-link target=_blank}:
+
+```
+.
+├── app
+│ ├── __init__.py
+│ └── main.py
+```
+У файлі `main.py` знаходиться Ваш застосунок **FastAPI** :
+
+{* ../../docs_src/app_testing/main.py *}
+
+### Файл тестування
+
+Ви можете створити файл `test_main.py` з Вашими тестами. Він може знаходитися в тому ж пакеті Python (у тій самій директорії з файлом `__init__.py`):
+
+
+``` hl_lines="5"
+.
+├── app
+│ ├── __init__.py
+│ ├── main.py
+│ └── test_main.py
+```
+
+Оскільки цей файл знаходиться в тому ж пакеті, Ви можете використовувати відносний імпорт, щоб імпортувати об'єкт `app` із модуля `main` (`main.py`):
+
+{* ../../docs_src/app_testing/test_main.py hl[3] *}
+
+
+...і написати код для тестів так само як і раніше.
+
+## Тестування: розширений приклад
+
+Тепер розширимо цей приклад і додамо більше деталей, щоб побачити, як тестувати різні частини.
+
+### Розширений файл застосунку **FastAPI**
+
+Залишимо ту саму структуру файлів:
+
+```
+.
+├── app
+│ ├── __init__.py
+│ ├── main.py
+│ └── test_main.py
+```
+
+Припустимо, що тепер файл `main.py` із Вашим **FastAPI**-застосунком містить додаткові операції шляху (**path operations**).
+
+Він має `GET`-операцію, яка може повертати помилку.
+
+Він має `POST`-операцію, яка може повертати кілька помилок.
+
+Обидві операції шляху вимагають заголовок `X-Token`.
+
+//// tab | Python 3.10+
+
+```Python
+{!> ../../docs_src/app_testing/app_b_an_py310/main.py!}
+```
+
+////
+
+//// tab | Python 3.9+
+
+```Python
+{!> ../../docs_src/app_testing/app_b_an_py39/main.py!}
+```
+
+////
+
+//// tab | Python 3.8+
+
+```Python
+{!> ../../docs_src/app_testing/app_b_an/main.py!}
+```
+
+////
+
+//// tab | Python 3.10+ non-Annotated
+
+/// tip | Порада
+
+Бажано використовувати версію з `Annotated`, якщо це можливо
+
+///
+
+```Python
+{!> ../../docs_src/app_testing/app_b_py310/main.py!}
+```
+
+////
+
+//// tab | Python 3.8+ non-Annotated
+
+/// tip | Порада
+
+Бажано використовувати версію з `Annotated`, якщо це можливо
+
+///
+
+```Python
+{!> ../../docs_src/app_testing/app_b/main.py!}
+```
+
+////
+
+### Розширений тестовий файл
+
+Потім Ви можете оновити `test_main.py`, додавши розширені тести:
+
+{* ../../docs_src/app_testing/app_b/test_main.py *}
+
+Коли Вам потрібно передати клієнту інформацію в запиті, але Ви не знаєте, як це зробити, Ви можете пошукати (наприклад, у Google) спосіб реалізації в `httpx`, або навіть у `requests`, оскільки HTTPX розроблений на основі дизайну Requests.
+
+Далі Ви просто повторюєте ці ж дії у ваших тестах.
+
+Наприклад:
+
+* Щоб передати *path* або *query* параметр, додайте його безпосередньо до URL.
+* Щоб передати тіло JSON, передайте Python-об'єкт (наприклад, `dict`) у параметр `json`.
+* Якщо потрібно надіслати *Form Data* замість JSON, використовуйте параметр `data`.
+* Щоб передати заголовки *headers*, використовуйте `dict` у параметрі `headers`.
+* Для *cookies* використовуйте `dict` у параметрі `cookies`.
+
+Докладніше про передачу даних у бекенд (за допомогою `httpx` або `TestClient`) можна знайти в документації HTTPX.
+
+/// info | Інформація
+
+Зверніть увагу, що `TestClient` отримує дані, які можна конвертувати в JSON, а не Pydantic-моделі.
+Якщо у Вас є Pydantic-модель у тесті, і Ви хочете передати її дані в додаток під час тестування, Ви можете використати `jsonable_encoder`, описаний у розділі [JSON Compatible Encoder](encoder.md){.internal-link target=_blank}.
+
+///
+
+## Запуск тестів
+
+Після цього вам потрібно встановити `pytest`.
+
+Переконайтеся, що Ви створили [віртуальне середовище]{.internal-link target=_blank}, активували його і встановили необхідні пакети, наприклад:
+
+
+
+```console
+$ pip install pytest
+
+---> 100%
+```
+
+
+
+`pytest` автоматично знайде файли з тестами, виконає їх і надасть вам результати.
+
+Запустіть тести за допомогою:
+
+
+
+```console
+$ pytest
+
+================ test session starts ================
+platform linux -- Python 3.6.9, pytest-5.3.5, py-1.8.1, pluggy-0.13.1
+rootdir: /home/user/code/superawesome-cli/app
+plugins: forked-1.1.3, xdist-1.31.0, cov-2.8.1
+collected 6 items
+
+---> 100%
+
+test_main.py ...... [100%]
+
+================= 1 passed in 0.03s =================
+```
+
+
From e992a2ec8bf8f5b40ec3fe9e3d38afcb6682b98c Mon Sep 17 00:00:00 2001
From: github-actions
Date: Fri, 28 Feb 2025 14:12:41 +0000
Subject: [PATCH 041/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 | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md
index 1c6bd259e..88357912d 100644
--- a/docs/en/docs/release-notes.md
+++ b/docs/en/docs/release-notes.md
@@ -11,6 +11,10 @@ hide:
* ⬆️ Bump Starlette to allow up to 0.46.0: `>=0.40.0,<0.47.0`. PR [#13426](https://github.com/fastapi/fastapi/pull/13426) by [@musicinmybrain](https://github.com/musicinmybrain).
+### Translations
+
+* 🌐 Add Ukrainian translation for `docs/uk/docs/tutorial/testing.md`. PR [#13371](https://github.com/fastapi/fastapi/pull/13371) by [@valentinDruzhinin](https://github.com/valentinDruzhinin).
+
## 0.115.9
### Fixes
From f4b4b0b0f5b7bc3b616d14d1ece7aa000100faa0 Mon Sep 17 00:00:00 2001
From: Valentyn
Date: Fri, 28 Feb 2025 09:13:50 -0500
Subject: [PATCH 042/352] =?UTF-8?q?=F0=9F=8C=90=20Add=20Ukrainian=20transl?=
=?UTF-8?q?ation=20for=20`docs/uk/docs/tutorial/request-forms.md`=20(#1338?=
=?UTF-8?q?3)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: Valentyn Druzhynin
Co-authored-by: Rostyslav
---
docs/uk/docs/tutorial/request-forms.md | 73 ++++++++++++++++++++++++++
1 file changed, 73 insertions(+)
create mode 100644 docs/uk/docs/tutorial/request-forms.md
diff --git a/docs/uk/docs/tutorial/request-forms.md b/docs/uk/docs/tutorial/request-forms.md
new file mode 100644
index 000000000..10c58a73e
--- /dev/null
+++ b/docs/uk/docs/tutorial/request-forms.md
@@ -0,0 +1,73 @@
+# Дані форми
+
+Якщо Вам потрібно отримувати поля форми замість JSON, Ви можете використовувати `Form`.
+
+/// info | Інформація
+
+Щоб використовувати форми, спочатку встановіть