committed by
GitHub
76 changed files with 2956 additions and 599 deletions
@ -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/[email protected] |
|||
# uses: cloudflare/wrangler-action@v3 |
|||
uses: cloudflare/wrangler-action@v3 |
|||
with: |
|||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} |
|||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} |
|||
|
After Width: | Height: | Size: 9.3 KiB |
After Width: | Height: | Size: 21 KiB |
@ -0,0 +1,77 @@ |
|||
# クッキーパラメータモデル |
|||
|
|||
もし関連する**複数のクッキー**から成るグループがあるなら、それらを宣言するために、**Pydanticモデル**を作成できます。🍪 |
|||
|
|||
こうすることで、**複数の場所**で**そのPydanticモデルを再利用**でき、バリデーションやメタデータを、すべてのクッキーパラメータに対して一度に宣言できます。😎 |
|||
|
|||
/// note | 備考 |
|||
|
|||
この機能は、FastAPIのバージョン `0.115.0` からサポートされています。🤓 |
|||
|
|||
/// |
|||
|
|||
/// tip | 豆知識 |
|||
|
|||
これと同じテクニックは `Query` 、 `Cookie` 、 `Header` にも適用できます。 😎 |
|||
|
|||
/// |
|||
|
|||
## クッキーにPydanticモデルを使用する |
|||
|
|||
必要な複数の**クッキー**パラメータを**Pydanticモデル**で宣言し、さらに、それを `Cookie` として宣言しましょう: |
|||
|
|||
{* ../../docs_src/cookie_param_models/tutorial001_an_py310.py hl[9:12,16] *} |
|||
|
|||
**FastAPI**は、リクエストの**クッキー**から**それぞれのフィールド**のデータを**抽出**し、定義された**Pydanticモデル**を提供します。 |
|||
|
|||
## ドキュメントの確認 |
|||
|
|||
対話的APIドキュメントUI `/docs` で、定義されているクッキーを確認できます: |
|||
|
|||
<div class="screenshot"> |
|||
<img src="/img/tutorial/cookie-param-models/image01.png"> |
|||
</div> |
|||
|
|||
/// info | 備考 |
|||
|
|||
|
|||
**ブラウザがクッキーを処理し**ていますが、特別な方法で内部的に処理を行っているために、**JavaScript**からは簡単に操作**できない**ことに留意してください。 |
|||
|
|||
**対話的APIドキュメントUI** `/docs` にアクセスすれば、*パスオペレーション*に関するクッキーの**ドキュメンテーション**を確認できます。 |
|||
|
|||
しかし、たとえ**クッキーデータを入力して**「Execute」をクリックしても、対話的APIドキュメントUIは**JavaScript**で動作しているためクッキーは送信されず、まるで値を入力しなかったかのような**エラー**メッセージが表示されます。 |
|||
|
|||
/// |
|||
|
|||
## 余分なクッキーを禁止する |
|||
|
|||
特定の(あまり一般的ではないかもしれない)ケースで、受け付けるクッキーを**制限**する必要があるかもしれません。 |
|||
|
|||
あなたのAPIは独自の <abbr title="念のためですが、これはジョークです。クッキー同意とは関係ありませんが、APIでさえ不適切なクッキーを拒否できるとは愉快ですね。クッキーでも食べてください。🍪 (原文: This is a joke, just in case. It has nothing to do with cookie consents, but it's funny that even the API can now reject the poor cookies. Have a cookie. 🍪)">クッキー同意</abbr> を管理する能力を持っています。 🤪🍪 |
|||
|
|||
Pydanticのモデルの Configuration を利用して、 `extra` フィールドを `forbid` とすることができます。 |
|||
|
|||
{* ../../docs_src/cookie_param_models/tutorial002_an_py39.py hl[10] *} |
|||
|
|||
もしクライアントが**余分なクッキー**を送ろうとすると、**エラー**レスポンスが返されます。 |
|||
|
|||
<abbr title="これもジョークです。気にしないでください。クッキーのお供にコーヒーでも飲んでください。☕ (原文: This is another joke. Don't pay attention to me. Have some coffee for your cookie. ☕)">どうせAPIに拒否されるのに</abbr>あなたの同意を得ようと精一杯努力する可哀想なクッキーバナーたち... 🍪 |
|||
|
|||
例えば、クライアントがクッキー `santa_tracker` を `good-list-please` という値で送ろうとすると、`santa_tracker` という <abbr title="サンタはクッキー不足を良しとはしないでしょう。🎅 はい、クッキージョークはもう止めておきます。(原文: Santa disapproves the lack of cookies. 🎅 Okay, no more cookie jokes.)">クッキーが許可されていない</abbr> ことを通知する**エラー**レスポンスが返されます: |
|||
|
|||
```json |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"type": "extra_forbidden", |
|||
"loc": ["cookie", "santa_tracker"], |
|||
"msg": "Extra inputs are not permitted", |
|||
"input": "good-list-please", |
|||
} |
|||
] |
|||
} |
|||
``` |
|||
|
|||
## まとめ |
|||
|
|||
**FastAPI**では、<abbr title="帰ってしまう前に最後のクッキーをどうぞ。🍪 (原文: Have a last cookie before you go. 🍪)">**クッキー**</abbr>を宣言するために、**Pydanticモデル**を使用できます。😎 |
@ -0,0 +1,68 @@ |
|||
# クエリパラメータモデル |
|||
|
|||
もし関連する**複数のクエリパラメータ**から成るグループがあるなら、それらを宣言するために、**Pydanticモデル**を作成できます。 |
|||
|
|||
こうすることで、**複数の場所**で**そのPydanticモデルを再利用**でき、バリデーションやメタデータを、すべてのクエリパラメータに対して一度に宣言できます。😎 |
|||
|
|||
/// note | 備考 |
|||
|
|||
この機能は、FastAPIのバージョン `0.115.0` からサポートされています。🤓 |
|||
|
|||
/// |
|||
|
|||
## クエリパラメータにPydanticモデルを使用する |
|||
|
|||
必要な**複数のクエリパラメータ**を**Pydanticモデル**で宣言し、さらに、それを `Query` として宣言しましょう: |
|||
|
|||
{* ../../docs_src/query_param_models/tutorial001_an_py310.py hl[9:13,17] *} |
|||
|
|||
**FastAPI**は、リクエストの**クエリパラメータ**からそれぞれの**フィールド**のデータを**抽出**し、定義された**Pydanticモデル**を提供します。 |
|||
|
|||
## ドキュメントの確認 |
|||
|
|||
対話的APIドキュメント `/docs` でクエリパラメータを確認できます: |
|||
|
|||
<div class="screenshot"> |
|||
<img src="/img/tutorial/query-param-models/image01.png"> |
|||
</div> |
|||
|
|||
## 余分なクエリパラメータを禁止する |
|||
|
|||
特定の(あまり一般的ではないかもしれない)ケースで、受け付けるクエリパラメータを**制限**する必要があるかもしれません。 |
|||
|
|||
Pydanticのモデルの Configuration を利用して、 `extra` フィールドを `forbid` とすることができます。 |
|||
|
|||
{* ../../docs_src/query_param_models/tutorial002_an_py310.py hl[10] *} |
|||
|
|||
もしクライアントが**クエリパラメータ**として**余分な**データを送ろうとすると、**エラー**レスポンスが返されます。 |
|||
|
|||
例えば、クライアントがクエリパラメータ `tool` に、値 `plumbus` を設定して送ろうとすると: |
|||
|
|||
```http |
|||
https://example.com/items/?limit=10&tool=plumbus |
|||
``` |
|||
|
|||
クエリパラメータ `tool` が許可されていないことを通知する**エラー**レスポンスが返されます。 |
|||
|
|||
```json |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"type": "extra_forbidden", |
|||
"loc": ["query", "tool"], |
|||
"msg": "Extra inputs are not permitted", |
|||
"input": "plumbus" |
|||
} |
|||
] |
|||
} |
|||
``` |
|||
|
|||
## まとめ |
|||
|
|||
**FastAPI**では、**クエリパラメータ**を宣言するために、**Pydanticモデル**を使用できます。😎 |
|||
|
|||
/// tip | 豆知識 |
|||
|
|||
ネタバレ注意: Pydanticモデルはクッキーやヘッダーの宣言にも使用できますが、その内容については後のチュートリアルで学びます。🤫 |
|||
|
|||
/// |
@ -0,0 +1,74 @@ |
|||
# Middleware (Промежуточный слой) |
|||
|
|||
Вы можете добавить промежуточный слой (middleware) в **FastAPI** приложение. |
|||
|
|||
"Middleware" это функция, которая выполняется с каждым запросом до его обработки какой-либо конкретной *операцией пути*. |
|||
А также с каждым ответом перед его возвращением. |
|||
|
|||
|
|||
* Она принимает каждый поступающий **запрос**. |
|||
* Может что-то сделать с этим **запросом** или выполнить любой нужный код. |
|||
* Затем передает **запрос** для последующей обработки (какой-либо *операцией пути*). |
|||
* Получает **ответ** (от *операции пути*). |
|||
* Может что-то сделать с этим **ответом** или выполнить любой нужный код. |
|||
* И возвращает **ответ**. |
|||
|
|||
/// note | Технические детали |
|||
|
|||
Если у вас есть зависимости с `yield`, то код выхода (код после `yield`) будет выполняться *после* middleware. |
|||
|
|||
Если у вас имеются некие фоновые задачи (см. документацию), то они будут запущены после middleware. |
|||
|
|||
/// |
|||
|
|||
## Создание middleware |
|||
|
|||
Для создания middleware используйте декоратор `@app.middleware("http")`. |
|||
|
|||
Функция middleware получает: |
|||
|
|||
* `request` (объект запроса). |
|||
* Функцию `call_next`, которая получает `request` в качестве параметра. |
|||
* Эта функция передаёт `request` соответствующей *операции пути*. |
|||
* Затем она возвращает ответ `response`, сгенерированный *операцией пути*. |
|||
* Также имеется возможность видоизменить `response`, перед тем как его вернуть. |
|||
|
|||
{* ../../docs_src/middleware/tutorial001.py hl[8:9,11,14] *} |
|||
|
|||
/// tip | Примечание |
|||
|
|||
Имейте в виду, что можно добавлять свои собственные заголовки <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" class="external-link" target="_blank">при помощи префикса 'X-'</a>. |
|||
|
|||
Если же вы хотите добавить собственные заголовки, которые клиент сможет увидеть в браузере, то вам потребуется добавить их в настройки CORS ([CORS (Cross-Origin Resource Sharing)](cors.md){.internal-link target=_blank}), используя параметр `expose_headers`, см. документацию <a href="https://www.starlette.io/middleware/#corsmiddleware" class="external-link" target="_blank">Starlette's CORS docs</a>. |
|||
|
|||
/// |
|||
|
|||
/// note | Технические детали |
|||
|
|||
Вы также можете использовать `from starlette.requests import Request`. |
|||
|
|||
**FastAPI** предоставляет такой доступ для удобства разработчиков. Но, на самом деле, это `Request` из Starlette. |
|||
|
|||
/// |
|||
|
|||
### До и после `response` |
|||
|
|||
Вы можете добавить код, использующий `request` до передачи его какой-либо *операции пути*. |
|||
|
|||
А также после формирования `response`, до того, как вы его вернёте. |
|||
|
|||
Например, вы можете добавить собственный заголовок `X-Process-Time`, содержащий время в секундах, необходимое для обработки запроса и генерации ответа: |
|||
|
|||
{* ../../docs_src/middleware/tutorial001.py hl[10,12:13] *} |
|||
|
|||
/// tip | Примечание |
|||
|
|||
Мы используем <a href="https://docs.python.org/3/library/time.html#time.perf_counter" class="external-link" target="_blank">`time.perf_counter()`</a> вместо `time.time()` для обеспечения большей точности наших примеров. 🤓 |
|||
|
|||
/// |
|||
|
|||
## Другие middleware |
|||
|
|||
О других middleware вы можете узнать больше в разделе [Advanced User Guide: Advanced Middleware](../advanced/middleware.md){.internal-link target=_blank}. |
|||
|
|||
В следующем разделе вы можете прочитать, как настроить <abbr title="Cross-Origin Resource Sharing">CORS</abbr> с помощью middleware. |
@ -0,0 +1,170 @@ |
|||
# Тіло запиту - Декілька параметрів |
|||
|
|||
Тепер, коли ми розглянули використання `Path` та `Query`, розгляньмо більш просунуті способи оголошення тіла запиту в **FastAPI**. |
|||
|
|||
## Змішування `Path`, `Query` та параметрів тіла запиту |
|||
|
|||
По-перше, звісно, Ви можете вільно змішувати оголошення параметрів `Path`, `Query` та тіла запиту, і **FastAPI** правильно їх обробить. |
|||
|
|||
Також Ви можете оголосити параметри тіла як необов’язкові, встановивши для них значення за замовчуванням `None`: |
|||
|
|||
{* ../../docs_src/body_multiple_params/tutorial001_an_py310.py hl[18:20] *} |
|||
|
|||
/// note | Примітка |
|||
|
|||
Зверніть увагу, що в цьому випадку параметр `item`, який береться з тіла запиту, є необов'язковим, оскільки має значення за замовчуванням `None`. |
|||
|
|||
/// |
|||
|
|||
## Декілька параметрів тіла запиту |
|||
|
|||
У попередньому прикладі *операція шляху* очікувала JSON з атрибутами `Item`, наприклад: |
|||
|
|||
```JSON |
|||
{ |
|||
"name": "Foo", |
|||
"description": "The pretender", |
|||
"price": 42.0, |
|||
"tax": 3.2 |
|||
} |
|||
``` |
|||
Але Ви також можете оголосити декілька параметрів тіла, наприклад `item` та `user`: |
|||
|
|||
{* ../../docs_src/body_multiple_params/tutorial002_py310.py hl[20] *} |
|||
|
|||
У цьому випадку **FastAPI** розпізнає, що є кілька параметрів тіла (два параметри є моделями Pydantic). |
|||
|
|||
Тому він використає назви параметрів як ключі (назви полів) у тілі запиту, очікуючи: |
|||
|
|||
```JSON |
|||
{ |
|||
"item": { |
|||
"name": "Foo", |
|||
"description": "The pretender", |
|||
"price": 42.0, |
|||
"tax": 3.2 |
|||
}, |
|||
"user": { |
|||
"username": "dave", |
|||
"full_name": "Dave Grohl" |
|||
} |
|||
} |
|||
``` |
|||
|
|||
/// note | Примітка |
|||
|
|||
Зверніть увагу, що хоча `item` оголошено, так само як і раніше, тепер він очікується в тілі під ключем `item`. |
|||
|
|||
/// |
|||
|
|||
**FastAPI** автоматично конвертує дані із запиту таким чином, щоб параметр `item` отримав свій вміст, і те ж саме стосується `user`. |
|||
|
|||
Він виконає валідацію складених даних і задокументує їх відповідним чином у схемі OpenAPI та в автоматичній документації. |
|||
|
|||
## Одиничні значення в тілі запиту |
|||
|
|||
Так само як є `Query` і `Path` для визначення додаткових даних для параметрів запиту та шляху, **FastAPI** надає еквівалентний `Body`. |
|||
|
|||
Наприклад, розширюючи попередню модель, Ви можете вирішити додати ще один ключ `importance` в те ж саме тіло запиту разом із `item` і `user`. |
|||
|
|||
Якщо Ви оголосите його як є, то, оскільки це одиничне значення, **FastAPI** припускатиме, що це параметр запиту (query parameter). |
|||
|
|||
Але Ви можете вказати **FastAPI** обробляти його як інший ключ тіла (body key), використовуючи `Body`: |
|||
|
|||
{* ../../docs_src/body_multiple_params/tutorial003_an_py310.py hl[23] *} |
|||
|
|||
У цьому випадку **FastAPI** очікуватиме тіло запиту у такому вигляді: |
|||
|
|||
```JSON |
|||
{ |
|||
"item": { |
|||
"name": "Foo", |
|||
"description": "The pretender", |
|||
"price": 42.0, |
|||
"tax": 3.2 |
|||
}, |
|||
"user": { |
|||
"username": "dave", |
|||
"full_name": "Dave Grohl" |
|||
}, |
|||
"importance": 5 |
|||
} |
|||
``` |
|||
Знову ж таки, **FastAPI** конвертуватиме типи даних, перевірятиме їх, створюватиме документацію тощо. |
|||
|
|||
## Декілька body та query параметрів |
|||
|
|||
Звісно, Ви можете оголошувати додаткові query параметри запиту, коли це необхідно, на додаток до будь-яких параметрів тіла запиту. |
|||
|
|||
Оскільки за замовчуванням окремі значення інтерпретуються як параметри запиту, Вам не потрібно явно додавати `Query`, можна просто використати: |
|||
|
|||
```Python |
|||
q: Union[str, None] = None |
|||
``` |
|||
|
|||
Або в Python 3.10 та вище: |
|||
|
|||
```Python |
|||
q: str | None = None |
|||
``` |
|||
|
|||
Наприклад: |
|||
|
|||
{* ../../docs_src/body_multiple_params/tutorial004_an_py310.py hl[28] *} |
|||
|
|||
|
|||
/// info | Інформація |
|||
|
|||
`Body` також має ті самі додаткові параметри валідації та метаданих, що й `Query`, `Path` та інші, які Ви побачите пізніше. |
|||
|
|||
/// |
|||
|
|||
## Вкладений поодинокий параметр тіла запиту |
|||
|
|||
Припустимо, у вас є лише один параметр тіла запиту `item` з моделі Pydantic `Item`. |
|||
|
|||
За замовчуванням **FastAPI** очікуватиме, що тіло запиту міститиме вміст безпосередньо. |
|||
|
|||
Але якщо Ви хочете, щоб він очікував JSON з ключем `item`, а всередині — вміст моделі (так, як це відбувається при оголошенні додаткових параметрів тіла), Ви можете використати спеціальний параметр `Body` — `embed`: |
|||
|
|||
```Python |
|||
item: Item = Body(embed=True) |
|||
``` |
|||
|
|||
як у прикладі: |
|||
|
|||
{* ../../docs_src/body_multiple_params/tutorial005_an_py310.py hl[17] *} |
|||
|
|||
У цьому випадку **FastAPI** очікуватиме тіло запиту такого вигляду: |
|||
|
|||
```JSON hl_lines="2" |
|||
{ |
|||
"item": { |
|||
"name": "Foo", |
|||
"description": "The pretender", |
|||
"price": 42.0, |
|||
"tax": 3.2 |
|||
} |
|||
} |
|||
``` |
|||
|
|||
замість: |
|||
|
|||
```JSON |
|||
{ |
|||
"name": "Foo", |
|||
"description": "The pretender", |
|||
"price": 42.0, |
|||
"tax": 3.2 |
|||
} |
|||
``` |
|||
|
|||
## Підсумок |
|||
|
|||
Ви можете додавати кілька параметрів тіла до Вашої *функції операції шляху* (*path operation function*), навіть якщо запит може мати лише одне тіло. |
|||
|
|||
Але **FastAPI** обробить це, надасть Вам потрібні дані у функції, перевірить їх та задокументує коректну схему в *операції шляху*. |
|||
|
|||
Також Ви можете оголошувати окремі значення, які будуть отримані як частина тіла запиту. |
|||
|
|||
Крім того, Ви можете вказати **FastAPI** вбудовувати тіло в ключ, навіть якщо оголошено лише один параметр. |
@ -0,0 +1,245 @@ |
|||
# Тіло запиту - Вкладені моделі |
|||
|
|||
З **FastAPI** Ви можете визначати, перевіряти, документувати та використовувати моделі, які можуть бути вкладені на будь-яку глибину (завдяки Pydantic). |
|||
|
|||
## Поля списку |
|||
|
|||
Ви можете визначити атрибут як підтип. Наприклад, Python-список (`list`): |
|||
|
|||
{* ../../docs_src/body_nested_models/tutorial001_py310.py hl[12] *} |
|||
|
|||
Це зробить `tags` списком, хоча не визначається тип елементів списку. |
|||
|
|||
## Поля списку з параметром типу |
|||
|
|||
Але Python має специфічний спосіб оголошення списків з внутрішніми типами або "параметрами типу": |
|||
### Імпортуємо `List` з модуля typing |
|||
|
|||
У Python 3.9 і вище можна використовувати стандартний `list` для оголошення таких типів, як ми побачимо нижче. 💡 |
|||
|
|||
Але в Python версії до 3.9 (від 3.6 і вище) спочатку потрібно імпортувати `List` з модуля стандартної бібліотеки Python `typing`: |
|||
|
|||
{* ../../docs_src/body_nested_models/tutorial002.py hl[1] *} |
|||
|
|||
### Оголошення `list` з параметром типу |
|||
|
|||
Щоб оголосити типи з параметрами типу (внутрішніми типами), такими як `list`, `dict`, `tuple`: |
|||
|
|||
* Якщо Ви використовуєте версію Python до 3.9, імпортуйте їх відповідну версію з модуля `typing`. |
|||
* Передайте внутрішні типи як "параметри типу", використовуючи квадратні дужки: `[` and `]`. |
|||
|
|||
У Python 3.9 це буде виглядати так: |
|||
|
|||
```Python |
|||
my_list: list[str] |
|||
``` |
|||
|
|||
У версіях Python до 3.9 це виглядає так: |
|||
|
|||
```Python |
|||
from typing import List |
|||
|
|||
my_list: List[str] |
|||
``` |
|||
|
|||
Це стандартний синтаксис Python для оголошення типів. |
|||
|
|||
Використовуйте той самий стандартний синтаксис для атрибутів моделей з внутрішніми типами. |
|||
|
|||
Отже, у нашому прикладі, ми можемо зробити `tags` саме "списком рядків": |
|||
|
|||
{* ../../docs_src/body_nested_models/tutorial002_py310.py hl[12] *} |
|||
|
|||
## Типи множин |
|||
|
|||
Але потім ми подумали, що теги не повинні повторюватися, вони, ймовірно, повинні бути унікальними рядками. |
|||
|
|||
І Python має спеціальний тип даних для множин унікальних елементів — це `set`. |
|||
|
|||
Тому ми можемо оголосити `tags` як множину рядків: |
|||
|
|||
{* ../../docs_src/body_nested_models/tutorial003_py310.py hl[12] *} |
|||
|
|||
Навіть якщо Ви отримаєте запит з дубльованими даними, він буде перетворений у множину унікальних елементів. |
|||
|
|||
І коли Ви будете виводити ці дані, навіть якщо джерело містить дублікати, вони будуть виведені як множина унікальних елементів. |
|||
|
|||
І це буде анотовано/документовано відповідно. |
|||
|
|||
## Вкладені моделі |
|||
|
|||
Кожен атрибут моделі Pydantic має тип. |
|||
|
|||
Але цей тип сам може бути іншою моделлю Pydantic. |
|||
|
|||
Отже, Ви можете оголосити глибоко вкладені JSON "об'єкти" з конкретними іменами атрибутів, типами та перевірками. |
|||
|
|||
Усе це, вкладене без обмежень. |
|||
|
|||
### Визначення підмоделі |
|||
|
|||
Наприклад, ми можемо визначити модель `Image`: |
|||
|
|||
{* ../../docs_src/body_nested_models/tutorial004_py310.py hl[7:9] *} |
|||
|
|||
### Використання підмоделі як типу |
|||
|
|||
А потім ми можемо використовувати її як тип атрибута: |
|||
|
|||
{* ../../docs_src/body_nested_models/tutorial004_py310.py hl[18] *} |
|||
|
|||
Це означатиме, що **FastAPI** очікуватиме тіло запиту такого вигляду: |
|||
|
|||
```JSON |
|||
{ |
|||
"name": "Foo", |
|||
"description": "The pretender", |
|||
"price": 42.0, |
|||
"tax": 3.2, |
|||
"tags": ["rock", "metal", "bar"], |
|||
"image": { |
|||
"url": "http://example.com/baz.jpg", |
|||
"name": "The Foo live" |
|||
} |
|||
} |
|||
``` |
|||
|
|||
Завдяки такій декларації у **FastAPI** Ви отримуєте: |
|||
|
|||
* Підтримку в редакторі (автозавершення тощо), навіть для вкладених моделей |
|||
* Конвертацію даних |
|||
* Валідацію даних |
|||
* Автоматичну документацію |
|||
|
|||
## Спеціальні типи та валідація |
|||
|
|||
Окрім звичайних типів, таких як `str`, `int`, `float`, та ін. Ви можете використовувати складніші типи, які наслідують `str`. |
|||
|
|||
Щоб побачити всі доступні варіанти, ознайомтеся з оглядом <a href="https://docs.pydantic.dev/latest/concepts/types/" class="external-link" target="_blank">типів у Pydantic</a>. Деякі приклади будуть у наступних розділах. |
|||
|
|||
Наприклад, у моделі `Image` є поле `url`, тому ми можемо оголосити його як `HttpUrl` від Pydantic замість `str`: |
|||
|
|||
{* ../../docs_src/body_nested_models/tutorial005_py310.py hl[2,8] *} |
|||
|
|||
Рядок буде перевірено як дійсну URL-адресу і задокументовано в JSON Schema / OpenAPI як URL. |
|||
|
|||
## Атрибути зі списками підмоделей |
|||
|
|||
У Pydantic Ви можете використовувати моделі як підтипи для `list`, `set` тощо: |
|||
|
|||
{* ../../docs_src/body_nested_models/tutorial006_py310.py hl[18] *} |
|||
|
|||
Це означає, що **FastAPI** буде очікувати (конвертувати, валідувати, документувати тощо) JSON тіло запиту у вигляді: |
|||
|
|||
```JSON hl_lines="11" |
|||
{ |
|||
"name": "Foo", |
|||
"description": "The pretender", |
|||
"price": 42.0, |
|||
"tax": 3.2, |
|||
"tags": [ |
|||
"rock", |
|||
"metal", |
|||
"bar" |
|||
], |
|||
"images": [ |
|||
{ |
|||
"url": "http://example.com/baz.jpg", |
|||
"name": "The Foo live" |
|||
}, |
|||
{ |
|||
"url": "http://example.com/dave.jpg", |
|||
"name": "The Baz" |
|||
} |
|||
] |
|||
} |
|||
``` |
|||
|
|||
/// info | Інформація |
|||
|
|||
Зверніть увагу, що тепер ключ `images` містить список об'єктів зображень. |
|||
|
|||
/// |
|||
|
|||
## Глибоко вкладені моделі |
|||
|
|||
Ви можете визначати вкладені моделі довільної глибини: |
|||
|
|||
{* ../../docs_src/body_nested_models/tutorial007_py310.py hl[7,12,18,21,25] *} |
|||
|
|||
/// info | Інформація |
|||
|
|||
Зверніть увагу, що в моделі `Offer` є список `Item`ів, які, своєю чергою, можуть мати необов'язковий список `Image`ів. |
|||
|
|||
/// |
|||
|
|||
## Тіла запитів, що складаються зі списків |
|||
|
|||
Якщо верхній рівень JSON тіла, яке Ви очікуєте, є JSON `масивом` (у Python — `list`), Ви можете оголосити тип у параметрі функції, як і в моделях Pydantic: |
|||
|
|||
```Python |
|||
images: List[Image] |
|||
``` |
|||
або в Python 3.9 і вище: |
|||
|
|||
```Python |
|||
images: list[Image] |
|||
``` |
|||
|
|||
наприклад: |
|||
|
|||
{* ../../docs_src/body_nested_models/tutorial008_py39.py hl[13] *} |
|||
|
|||
## Підтримка в редакторі всюди |
|||
|
|||
Ви отримаєте підтримку в редакторі всюди. |
|||
|
|||
Навіть для елементів у списках: |
|||
|
|||
<img src="/img/tutorial/body-nested-models/image01.png"> |
|||
|
|||
Ви не змогли б отримати таку підтримку в редакторі, якби працювали напряму зі `dict`, а не з моделями Pydantic. |
|||
|
|||
Але Вам не потрібно турбуватися про це: вхідні dict'и автоматично конвертуються, а вихідні дані автоматично перетворюються в JSON. |
|||
|
|||
## Тіла з довільними `dict` |
|||
|
|||
Ви також можете оголосити тіло як `dict` з ключами одного типу та значеннями іншого типу. |
|||
|
|||
Це корисно, якщо Ви не знаєте наперед, які імена полів будуть дійсними (як у випадку з моделями Pydantic). |
|||
|
|||
Це буде корисно, якщо Ви хочете приймати ключі, які заздалегідь невідомі. |
|||
|
|||
--- |
|||
|
|||
Це також зручно, якщо Ви хочете мати ключі іншого типу (наприклад, `int`). |
|||
|
|||
Ось що ми розглянемо далі. |
|||
|
|||
У цьому випадку Ви можете приймати будь-який `dict`, якщо його ключі — це `int`, а значення — `float`: |
|||
|
|||
{* ../../docs_src/body_nested_models/tutorial009_py39.py hl[7] *} |
|||
|
|||
/// tip | Порада |
|||
|
|||
Майте на увазі, що в JSON тілі ключі можуть бути лише рядками (`str`). |
|||
|
|||
Але Pydantic автоматично конвертує дані. |
|||
|
|||
Це означає, що навіть якщо клієнти вашого API надсилатимуть ключі у вигляді рядків, якщо вони містять цілі числа, Pydantic конвертує їх і проведе валідацію. |
|||
|
|||
Тобто `dict`, який Ви отримаєте як `weights`, матиме ключі типу `int` та значення типу `float`. |
|||
|
|||
/// |
|||
|
|||
## Підсумок |
|||
|
|||
З **FastAPI** Ви маєте максимальну гнучкість завдяки моделям Pydantic, зберігаючи при цьому код простим, коротким та елегантним. |
|||
|
|||
А також отримуєте всі переваги: |
|||
|
|||
* Підтримка в редакторі (автодоповнення всюди!) |
|||
* Конвертація даних (парсинг/сериалізація) |
|||
* Валідація даних |
|||
* Документація схем |
|||
* Автоматичне створення документації |
@ -0,0 +1,112 @@ |
|||
# Налагодження (Debugging) |
|||
|
|||
Ви можете під'єднати дебагер у Вашому редакторі коду, наприклад, у Visual Studio Code або PyCharm. |
|||
|
|||
## Виклик `uvicorn` |
|||
|
|||
У Вашому FastAPI-додатку імпортуйте та запустіть `uvicorn` безпосередньо: |
|||
|
|||
{* ../../docs_src/debugging/tutorial001.py hl[1,15] *} |
|||
|
|||
### Про `__name__ == "__main__"` |
|||
|
|||
Головна мета використання `__name__ == "__main__"` — це забезпечення виконання певного коду тільки тоді, коли файл запускається безпосередньо: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ python myapp.py |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
але не виконується при його імпорті в інший файл, наприклад: |
|||
|
|||
```Python |
|||
from myapp import app |
|||
``` |
|||
|
|||
#### Детальніше |
|||
|
|||
Припустимо, Ваш файл називається `myapp.py`. |
|||
|
|||
Якщо Ви запустите його так: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ python myapp.py |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
тоді внутрішня змінна `__name__`, яка створюється автоматично Python, матиме значення `"__main__"`. |
|||
|
|||
Отже, цей блок коду: |
|||
|
|||
```Python |
|||
uvicorn.run(app, host="0.0.0.0", port=8000) |
|||
``` |
|||
|
|||
буде виконаний. |
|||
|
|||
--- |
|||
|
|||
Це не станеться, якщо Ви імпортуєте цей модуль (файл). |
|||
|
|||
Якщо у Вас є інший файл, наприклад `importer.py`, з наступним кодом: |
|||
|
|||
```Python |
|||
from myapp import app |
|||
|
|||
# Додатковий код |
|||
``` |
|||
|
|||
У цьому випадку автоматично створена змінна у файлі `myapp.py` не матиме значення змінної `__name__` як `"__main__"`. |
|||
|
|||
Отже, рядок: |
|||
|
|||
```Python |
|||
uvicorn.run(app, host="0.0.0.0", port=8000) |
|||
``` |
|||
|
|||
не буде виконано. |
|||
|
|||
/// info | Інформація |
|||
|
|||
Більш детальну інформацію можна знайти в <a href="https://docs.python.org/3/library/__main__.html" class="external-link" target="_blank">офіційній документації Python</a>. |
|||
|
|||
/// |
|||
|
|||
## Запуск коду з вашим дебагером |
|||
|
|||
Оскільки Ви запускаєте сервер Uvicorn безпосередньо з Вашого коду, Ви можете запустити вашу Python програму (ваш FastAPI додаток) безпосередньо з дебагера. |
|||
|
|||
--- |
|||
|
|||
Наприклад, у Visual Studio Code Ви можете: |
|||
|
|||
* Перейдіть на вкладку "Debug". |
|||
* Натисніть "Add configuration...". |
|||
* Виберіть "Python" |
|||
* Запустіть дебагер з опцією "`Python: Current File (Integrated Terminal)`". |
|||
|
|||
Це запустить сервер з Вашим **FastAPI** кодом, зупиниться на точках зупину тощо. |
|||
|
|||
Ось як це може виглядати: |
|||
|
|||
<img src="/img/tutorial/debugging/image01.png"> |
|||
|
|||
--- |
|||
Якщо Ви використовуєте PyCharm, ви можете: |
|||
|
|||
* Відкрити меню "Run". |
|||
* Вибрати опцію "Debug...". |
|||
* Потім з'явиться контекстне меню. |
|||
* Вибрати файл для налагодження (у цьому випадку, `main.py`). |
|||
|
|||
Це запустить сервер з Вашим **FastAPI** кодом, зупиниться на точках зупину тощо. |
|||
|
|||
Ось як це може виглядати: |
|||
|
|||
<img src="/img/tutorial/debugging/image02.png"> |
@ -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** автоматично конвертує їх. |
@ -0,0 +1,261 @@ |
|||
# Path Параметри |
|||
|
|||
Ви можете визначити "параметри" або "змінні" шляху, використовуючи синтаксис форматованих рядків: |
|||
|
|||
{* ../../docs_src/path_params/tutorial001.py hl[6:7] *} |
|||
|
|||
Значення параметра шляху `item_id` передається у функцію як аргумент `item_id`. |
|||
|
|||
Якщо запустити цей приклад та перейти за посиланням <a href="http://127.0.0.1:8000/items/foo" class="external-link" target="_blank">http://127.0.0.1:8000/items/foo</a>, то отримаємо таку відповідь: |
|||
|
|||
```JSON |
|||
{"item_id":"foo"} |
|||
``` |
|||
|
|||
## Path параметри з типами |
|||
|
|||
Ви можете визначити тип параметра шляху у функції, використовуючи стандартні анотації типів Python: |
|||
|
|||
{* ../../docs_src/path_params/tutorial002.py hl[7] *} |
|||
|
|||
У такому випадку `item_id` визначається як `int`. |
|||
|
|||
/// check | Примітка |
|||
|
|||
Це дасть можливість підтримки редактора всередині функції з перевірками помилок, автодоповнення тощо. |
|||
|
|||
/// |
|||
|
|||
## <abbr title="або: серіалізація, парсинг, маршалізація">Перетворення</abbr> даних |
|||
|
|||
Якщо запустити цей приклад і перейти за посиланням <a href="http://127.0.0.1:8000/items/3" class="external-link" target="_blank">http://127.0.0.1:8000/items/3</a>, то отримаєте таку відповідь: |
|||
|
|||
```JSON |
|||
{"item_id":3} |
|||
``` |
|||
|
|||
/// check | Примітка |
|||
|
|||
Зверніть увагу, що значення, яке отримала (і повернула) ваша функція, — це `3`. Це Python `int`, а не рядок `"3"`. |
|||
|
|||
Отже, з таким оголошенням типу **FastAPI** автоматично виконує <abbr title="перетворення рядка, що надходить із HTTP-запиту, у типи даних Python">"парсинг"</abbr> запитів. |
|||
|
|||
/// |
|||
|
|||
## <abbr title="Або валідація">Перевірка</abbr> даних |
|||
|
|||
Якщо ж відкрити у браузері посилання <a href="http://127.0.0.1:8000/items/foo" class="external-link" target="_blank">http://127.0.0.1:8000/items/foo</a>, то побачимо цікаву HTTP-помилку: |
|||
|
|||
```JSON |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"type": "int_parsing", |
|||
"loc": [ |
|||
"path", |
|||
"item_id" |
|||
], |
|||
"msg": "Input should be a valid integer, unable to parse string as an integer", |
|||
"input": "foo", |
|||
"url": "https://errors.pydantic.dev/2.1/v/int_parsing" |
|||
} |
|||
] |
|||
} |
|||
``` |
|||
тому що параметр шляху має значення `"foo"`, яке не є типом `int`. |
|||
|
|||
Таку саму помилку отримаємо, якщо передати `float` замість `int`, як бачимо, у цьому прикладі: <a href="http://127.0.0.1:8000/items/4.2" class="external-link" target="_blank">http://127.0.0.1:8000/items/4.2</a> |
|||
|
|||
/// check | Примітка |
|||
|
|||
Отже, **FastAPI** надає перевірку типів з таким самим оголошенням типу в Python. |
|||
|
|||
Зверніть увагу, що помилка також чітко вказує саме на те місце, де валідація не пройшла. |
|||
|
|||
Це неймовірно корисно під час розробки та дебагінгу коду, що взаємодіє з вашим API. |
|||
|
|||
/// |
|||
|
|||
## Документація |
|||
|
|||
Тепер коли відкриєте свій браузер за посиланням <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>, то побачите автоматично згенеровану, інтерактивну API-документацію: |
|||
|
|||
<img src="/img/tutorial/path-params/image01.png"> |
|||
|
|||
/// check | Примітка |
|||
|
|||
Знову ж таки, лише з цим самим оголошенням типу в Python, FastAPI надає вам автоматичну, інтерактивну документацію (з інтеграцією Swagger UI). |
|||
|
|||
Зверніть увагу, що параметр шляху оголошений як ціле число. |
|||
|
|||
|
|||
/// |
|||
|
|||
## Переваги стандартизації, альтернативна документація |
|||
|
|||
І оскільки згенерована схема відповідає стандарту <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md" class="external-link" target="_blank">OpenAPI</a>, існує багато сумісних інструментів. |
|||
|
|||
З цієї причини FastAPI також надає альтернативну документацію API (використовуючи ReDoc), до якої можна отримати доступ за посиланням <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>: |
|||
|
|||
<img src="/img/tutorial/path-params/image02.png"> |
|||
|
|||
Таким чином, існує багато сумісних інструментів, включаючи інструменти для генерації коду для багатьох мов. |
|||
|
|||
|
|||
## Pydantic |
|||
|
|||
Вся валідація даних виконується за лаштунками за допомогою <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a>, тому Ви отримуєте всі переваги від його використання. І можете бути впевнені, що все в надійних руках. |
|||
|
|||
Ви можете використовувати ті самі оголошення типів з `str`, `float`, `bool` та багатьма іншими складними типами даних. |
|||
|
|||
Декілька з них будуть розглянуті в наступних розділах посібника. |
|||
|
|||
## Порядок має значення |
|||
|
|||
При створенні *операцій шляху* можуть виникати ситуації, коли шлях фіксований. |
|||
|
|||
Наприклад, `/users/me`. Припустимо, що це шлях для отримання даних про поточного користувача. |
|||
|
|||
А також у вас може бути шлях `/users/{user_id}`, щоб отримати дані про конкретного користувача за його ID. |
|||
|
|||
Оскільки *операції шляху* оцінюються по черзі, Ви повинні переконатися, що шлях для `/users/me` оголошений перед шляхом для `/users/{user_id}`: |
|||
|
|||
{* ../../docs_src/path_params/tutorial003.py hl[6,11] *} |
|||
|
|||
Інакше шлях для `/users/{user_id}` також буде відповідати для `/users/me`, "вважаючи", що він отримує параметр `user_id` зі значенням `"me"`. |
|||
|
|||
Аналогічно, Ви не можете оголосити операцію шляху: |
|||
|
|||
{* ../../docs_src/path_params/tutorial003b.py hl[6,11] *} |
|||
|
|||
Перша операція буде завжди використовуватися, оскільки шлях збігається першим. |
|||
## Попередньо визначені значення |
|||
|
|||
Якщо у вас є *операція шляху*, яка приймає *параметр шляху*, але Ви хочете, щоб можливі допустимі значення *параметра шляху* були попередньо визначені, Ви можете використати стандартний Python <abbr title="перелічення">Enum</abbr>. |
|||
|
|||
### Створення класу `Enum` |
|||
|
|||
Імпортуйте `Enum` і створіть підклас, що наслідується від `str` та `Enum`. |
|||
|
|||
Наслідуючи від `str`, документація API зможе визначити, що значення повинні бути типу `string`, і правильно їх відобразить. |
|||
|
|||
Після цього створіть атрибути класу з фіксованими значеннями, які будуть доступними допустимими значеннями: |
|||
|
|||
{* ../../docs_src/path_params/tutorial005.py hl[1,6:9] *} |
|||
|
|||
/// info | Додаткова інформація |
|||
|
|||
<a href="https://docs.python.org/3/library/enum.html" class="external-link" target="_blank">Перелічення (або enums) доступні в Python</a> починаючи з версії 3.4. |
|||
|
|||
/// |
|||
|
|||
/// tip | Порада |
|||
|
|||
Якщо вам цікаво, "AlexNet", "ResNet" та "LeNet" — це просто назви ML моделей <abbr title="Технічно, архітектури Deep Learning моделей">Machine Learning</abbr>. |
|||
|
|||
/// |
|||
|
|||
|
|||
### Оголосіть *параметр шляху* |
|||
|
|||
Потім створіть *параметр шляху* з анотацією типу, використовуючи створений вами клас enum (`ModelName`): |
|||
|
|||
{* ../../docs_src/path_params/tutorial005.py hl[16] *} |
|||
|
|||
### Перевірка документації |
|||
|
|||
Оскільки доступні значення для *параметра шляху* визначені заздалегідь, інтерактивна документація зможе красиво їх відобразити: |
|||
|
|||
<img src="/img/tutorial/path-params/image03.png"> |
|||
|
|||
### Робота з *перелічуваннями* у Python |
|||
|
|||
Значення *параметра шляху* буде елементом *перелічування*. |
|||
|
|||
#### Порівняння *елементів перелічування* |
|||
|
|||
Ви можете порівнювати його з *елементами перелічування* у створеному вами enum `ModelName`: |
|||
|
|||
{* ../../docs_src/path_params/tutorial005.py hl[17] *} |
|||
|
|||
#### Отримання *значення перелічування* |
|||
|
|||
Ви можете отримати фактичне значення (у цьому випадку це `str`), використовуючи `model_name.value`, або загалом `your_enum_member.value`: |
|||
|
|||
{* ../../docs_src/path_params/tutorial005.py hl[20] *} |
|||
|
|||
/// tip | Порада |
|||
|
|||
Ви також можете отримати доступ до значення `"lenet"`, використовуючи `ModelName.lenet.value`. |
|||
|
|||
/// |
|||
|
|||
|
|||
#### Повернення *елементів перелічування* |
|||
|
|||
Ви можете повертати *елементи перелічування* з вашої *операції шляху*, навіть вкладені у JSON-тіло (наприклад, `dict`). |
|||
|
|||
Вони будуть перетворені на відповідні значення (у цьому випадку рядки) перед поверненням клієнту: |
|||
|
|||
{* ../../docs_src/path_params/tutorial005.py hl[18,21,23] *} |
|||
|
|||
На стороні клієнта Ви отримаєте відповідь у форматі JSON, наприклад: |
|||
|
|||
```JSON |
|||
{ |
|||
"model_name": "alexnet", |
|||
"message": "Deep Learning FTW!" |
|||
} |
|||
``` |
|||
|
|||
## Path-параметри, що містять шляхи |
|||
|
|||
Припустимо, у вас є *операція шляху* з маршрутом `/files/{file_path}`. |
|||
|
|||
Але вам потрібно, щоб `file_path` містив *шлях*, наприклад `home/johndoe/myfile.txt`. |
|||
|
|||
Отже, URL для цього файлу виглядатиме так: `/files/home/johndoe/myfile.txt`. |
|||
|
|||
|
|||
|
|||
### Підтримка OpenAPI |
|||
|
|||
OpenAPI не підтримує спосіб оголошення *параметра шляху*, що містить *шлях* всередині, оскільки це може призвести до сценаріїв, які складно тестувати та визначати. |
|||
|
|||
Однак (одначе), Ви все одно можете зробити це в **FastAPI**, використовуючи один із внутрішніх інструментів Starlette. |
|||
|
|||
Документація все ще працюватиме, хоча й не додаватиме опису про те, що параметр повинен містити шлях. |
|||
|
|||
### Конвертер шляху |
|||
|
|||
Використовуючи опцію безпосередньо зі Starlette, Ви можете оголосити *параметр шляху*, що містить *шлях*, використовуючи URL на кшталт: |
|||
|
|||
``` |
|||
/files/{file_path:path} |
|||
``` |
|||
У цьому випадку ім'я параметра — `file_path`, а остання частина `:path` вказує на те, що параметр повинен відповідати будь-якому *шляху*. |
|||
|
|||
Отже, Ви можете використати його так: |
|||
|
|||
{* ../../docs_src/path_params/tutorial004.py hl[6] *} |
|||
|
|||
/// tip | Порада |
|||
|
|||
Вам може знадобитися, щоб параметр містив `/home/johndoe/myfile.txt` із початковою косою рискою (`/`). |
|||
|
|||
У такому випадку URL виглядатиме так: `/files//home/johndoe/myfile.txt`, із подвійною косою рискою (`//`) між `files` і `home`. |
|||
|
|||
/// |
|||
|
|||
## Підсумок |
|||
|
|||
З **FastAPI**, використовуючи короткі, інтуїтивно зрозумілі та стандартні оголошення типів Python, Ви отримуєте: |
|||
|
|||
* Підтримку в редакторі: перевірка помилок, автодоповнення тощо. |
|||
* "<abbr title="перетворення рядка, що надходить з HTTP-запиту, у типи даних Python">Парсинг</abbr>" даних |
|||
* Валідацію даних |
|||
* Анотацію API та автоматичну документацію |
|||
|
|||
І вам потрібно оголосити їх лише один раз. |
|||
|
|||
Це, ймовірно, основна видима перевага **FastAPI** порівняно з альтернативними фреймворками (окрім високої продуктивності). |
@ -0,0 +1,192 @@ |
|||
# Query Параметри |
|||
|
|||
Коли Ви оголошуєте інші параметри функції, які не є частиною параметрів шляху, вони автоматично інтерпретуються як "query" параметри. |
|||
|
|||
{* ../../docs_src/query_params/tutorial001.py hl[9] *} |
|||
|
|||
Query параметри — це набір пар ключ-значення, що йдуть після символу `?` в URL, розділені символами `&`. |
|||
|
|||
Наприклад, в URL: |
|||
|
|||
``` |
|||
http://127.0.0.1:8000/items/?skip=0&limit=10 |
|||
``` |
|||
|
|||
...query параметрами є: |
|||
|
|||
* `skip`: зі значенням `0` |
|||
* `limit`: зі значенням `10` |
|||
|
|||
Оскільки вони є частиною URL, вони "за замовчуванням" є рядками. |
|||
|
|||
Але коли Ви оголошуєте їх із типами Python (у наведеному прикладі як `int`), вони перетворюються на цей тип і проходять перевірку відповідності. |
|||
|
|||
Увесь той самий процес, який застосовується до параметрів шляху, також застосовується до query параметрів: |
|||
|
|||
* Підтримка в редакторі (автодоповнення, перевірка помилок) |
|||
* <abbr title="перетворення рядка, що надходить з HTTP-запиту, у типи даних Python">"Парсинг"</abbr> даних |
|||
* Валідація даних |
|||
* Автоматична документація |
|||
|
|||
|
|||
## Значення за замовчуванням |
|||
|
|||
Оскільки query параметри не є фіксованою частиною шляху, вони можуть бути необов’язковими та мати значення за замовчуванням. |
|||
|
|||
У наведеному вище прикладі вони мають значення за замовчуванням: `skip=0` і `limit=10`. |
|||
|
|||
Отже, результат переходу за URL: |
|||
|
|||
``` |
|||
http://127.0.0.1:8000/items/ |
|||
``` |
|||
буде таким самим, як і перехід за посиланням: |
|||
|
|||
``` |
|||
http://127.0.0.1:8000/items/?skip=0&limit=10 |
|||
``` |
|||
|
|||
Але якщо Ви перейдете, наприклад, за посиланням: |
|||
|
|||
``` |
|||
http://127.0.0.1:8000/items/?skip=20 |
|||
``` |
|||
|
|||
Значення параметрів у вашій функції будуть такими: |
|||
|
|||
* `skip=20`: оскільки Ви вказали його в URL |
|||
* `limit=10`: оскільки це значення за замовчуванням |
|||
|
|||
## Необов'язкові параметри |
|||
|
|||
Аналогічно, Ви можете оголосити необов’язкові query параметри, встановивши для них значення за замовчуванням `None`: |
|||
|
|||
{* ../../docs_src/query_params/tutorial002_py310.py hl[7] *} |
|||
|
|||
У цьому випадку параметр функції `q` буде необов’язковим і за замовчуванням матиме значення `None`. |
|||
|
|||
/// check | Примітка |
|||
|
|||
Також зверніть увагу, що **FastAPI** достатньо розумний, щоб визначити, що параметр шляху `item_id` є параметром шляху, а `q` — ні, отже, це query параметр. |
|||
|
|||
/// |
|||
|
|||
## Перетворення типу Query параметра |
|||
|
|||
Ви також можете оголошувати параметри типу `bool`, і вони будуть автоматично конвертовані: |
|||
|
|||
{* ../../docs_src/query_params/tutorial003_py310.py hl[7] *} |
|||
|
|||
У цьому випадку, якщо Ви звернетесь до: |
|||
|
|||
|
|||
``` |
|||
http://127.0.0.1:8000/items/foo?short=1 |
|||
``` |
|||
|
|||
або |
|||
|
|||
``` |
|||
http://127.0.0.1:8000/items/foo?short=True |
|||
``` |
|||
|
|||
або |
|||
|
|||
``` |
|||
http://127.0.0.1:8000/items/foo?short=true |
|||
``` |
|||
|
|||
або |
|||
|
|||
``` |
|||
http://127.0.0.1:8000/items/foo?short=on |
|||
``` |
|||
|
|||
або |
|||
|
|||
``` |
|||
http://127.0.0.1:8000/items/foo?short=yes |
|||
``` |
|||
|
|||
або будь-який інший варіант написання (великі літери, перша літера велика тощо), ваша функція побачить параметр `short` зі значенням `True` з типом даних `bool`. В іншому випадку – `False`. |
|||
|
|||
## Кілька path і query параметрів |
|||
|
|||
Ви можете одночасно оголошувати кілька path і query параметрів, і **FastAPI** автоматично визначить, який з них до чого належить. |
|||
|
|||
|
|||
Не потрібно дотримуватись певного порядку їх оголошення. |
|||
|
|||
Вони визначаються за назвою: |
|||
|
|||
{* ../../docs_src/query_params/tutorial004_py310.py hl[6,8] *} |
|||
|
|||
## Обов’язкові Query параметри |
|||
|
|||
Якщо Ви оголошуєте значення за замовчуванням для параметрів, які не є path-параметрами (у цьому розділі ми бачили поки що лише path параметри), тоді вони стають необов’язковими. |
|||
|
|||
Якщо Ви не хочете вказувати конкретні значення, але хочете зробити параметр опціональним, задайте `None` як значення за замовчуванням. |
|||
|
|||
Але якщо Ви хочете зробити query параметр обов’язковим, просто не вказуйте для нього значення за замовчуванням: |
|||
|
|||
{* ../../docs_src/query_params/tutorial005.py hl[6:7] *} |
|||
|
|||
Тут `needy` – обов’язковий query параметр типу `str`. |
|||
|
|||
Якщо Ви відкриєте у браузері URL-адресу: |
|||
|
|||
``` |
|||
http://127.0.0.1:8000/items/foo-item |
|||
``` |
|||
|
|||
...без додавання обов’язкового параметра `needy`, Ви побачите помилку: |
|||
|
|||
```JSON |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"type": "missing", |
|||
"loc": [ |
|||
"query", |
|||
"needy" |
|||
], |
|||
"msg": "Field required", |
|||
"input": null, |
|||
"url": "https://errors.pydantic.dev/2.1/v/missing" |
|||
} |
|||
] |
|||
} |
|||
``` |
|||
|
|||
Оскільки `needy` є обов’язковим параметром, вам потрібно вказати його в URL: |
|||
|
|||
``` |
|||
http://127.0.0.1:8000/items/foo-item?needy=sooooneedy |
|||
``` |
|||
|
|||
...цей запит поверне: |
|||
|
|||
```JSON |
|||
{ |
|||
"item_id": "foo-item", |
|||
"needy": "sooooneedy" |
|||
} |
|||
``` |
|||
|
|||
|
|||
Звичайно, Ви можете визначити деякі параметри як обов’язкові, інші зі значенням за замовчуванням, а ще деякі — повністю опціональні: |
|||
|
|||
{* ../../docs_src/query_params/tutorial006_py310.py hl[8] *} |
|||
|
|||
У цьому випадку є 3 query параметри: |
|||
|
|||
* `needy`, обов’язковий `str`. |
|||
* `skip`, `int` зі значенням за замовчуванням `0`. |
|||
* `limit`, опціональний `int`. |
|||
|
|||
|
|||
/// tip | Підказка |
|||
|
|||
Ви також можете використовувати `Enum`-и, так само як і з [Path Parameters](path-params.md#predefined-values){.internal-link target=_blank}. |
|||
|
|||
/// |
@ -0,0 +1,175 @@ |
|||
# Запит файлів |
|||
|
|||
Ви можете визначити файли, які будуть завантажуватися клієнтом, використовуючи `File`. |
|||
|
|||
/// info | Інформація |
|||
|
|||
Щоб отримувати завантажені файли, спочатку встановіть <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">python-multipart</a>. |
|||
|
|||
Переконайтеся, що Ви створили [віртуальне середовище](../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()` у значенні за замовчуванням параметра. |
|||
* Використовується "буферизований" файл: |
|||
* Файл зберігається в пам'яті до досягнення певного обмеження, після чого він записується на диск. |
|||
* Це означає, що він добре працює для великих файлів, таких як зображення, відео, великі двійкові файли тощо, не споживаючи всю пам'ять. |
|||
Ви можете отримати метадані про завантажений файл. |
|||
* Він має <a href="https://docs.python.org/3/glossary.html#term-file-like-object" class="external-link" target="_blank">file-like</a> `асинхронний файловий інтерфейс` interface. |
|||
* Він надає фактичний об'єкт Python <a href="https://docs.python.org/3/library/tempfile.html#tempfile.SpooledTemporaryFile" class="external-link" target="_blank">`SpooledTemporaryFile`</a>, який можна передавати безпосередньо іншим бібліотекам. |
|||
|
|||
### `UploadFile` |
|||
|
|||
`UploadFile` має такі атрибути: |
|||
|
|||
* `filename`: Рядок `str` з оригінальною назвою файлу, який був завантажений (наприклад, `myimage.jpg`). |
|||
* `content_type`: Рядок `str` з MIME-типом (наприклад, `image/jpeg`). |
|||
* `file`: Об'єкт <a href="https://docs.python.org/3/library/tempfile.html#tempfile.SpooledTemporaryFile" class="external-link" target="_blank">SpooledTemporaryFile</a> (<a href="https://docs.python.org/3/glossary.html#term-file-like-object" class="external-link" target="_blank">файлоподібний</a> об'єкт). Це фактичний файловий об'єкт 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-форми (`<form></form>`) надсилають дані на сервер, зазвичай використовує "спеціальне" кодування, відмінне від JSON. |
|||
|
|||
**FastAPI** забезпечує правильне зчитування цих даних з відповідної частини запиту, а не з JSON. |
|||
|
|||
/// note | Технічні деталі |
|||
|
|||
Дані з форм зазвичай кодуються за допомогою "media type" `application/x-www-form-urlencoded`, якщо вони не містять файлів. |
|||
|
|||
Але якщо форма містить файли, вона кодується у форматі `multipart/form-data`. Якщо Ви використовуєте `File`, **FastAPI** визначить, що потрібно отримати файли з відповідної частини тіла запиту. |
|||
|
|||
Щоб дізнатися більше про ці типи кодування та формові поля, ознайомтеся з <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" class="external-link" target="_blank"><abbr title="Mozilla Developer Network">документацією MDN</abbr> щодо <code>POST</code></a>. |
|||
|
|||
/// |
|||
|
|||
/// 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. |
@ -0,0 +1,73 @@ |
|||
# Дані форми |
|||
|
|||
Якщо Вам потрібно отримувати поля форми замість JSON, Ви можете використовувати `Form`. |
|||
|
|||
/// info | Інформація |
|||
|
|||
Щоб використовувати форми, спочатку встановіть <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>. |
|||
|
|||
Переконайтеся, що Ви створили [віртуальне середовище](../virtual-environments.md){.internal-link target=_blank}, активували його, і потім встановили бібліотеку, наприклад: |
|||
|
|||
```console |
|||
$ pip install python-multipart |
|||
``` |
|||
|
|||
/// |
|||
|
|||
## Імпорт `Form` |
|||
|
|||
Імпортуйте `Form` з `fastapi`: |
|||
|
|||
{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[3] *} |
|||
|
|||
## Оголошення параметрів `Form` |
|||
|
|||
Створюйте параметри форми так само як Ви б створювали `Body` або `Query`: |
|||
|
|||
{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[9] *} |
|||
|
|||
Наприклад, один зі способів використання специфікації OAuth2 (так званий "password flow") вимагає надсилати `username` та `password` як поля форми. |
|||
|
|||
<abbr title="Специфікація">spec</abbr> вимагає, щоб ці поля мали точні назви `username` і `password` та надсилалися у вигляді полів форми, а не JSON. |
|||
|
|||
З `Form` Ви можете оголошувати ті ж конфігурації, що і з `Body` (та `Query`, `Path`, `Cookie`), включаючи валідацію, приклади, псевдоніми (наприклад, `user-name` замість `username`) тощо. |
|||
|
|||
/// info | Інформація |
|||
|
|||
`Form` — це клас, який безпосередньо наслідується від `Body`. |
|||
|
|||
/// |
|||
|
|||
/// tip | Порада |
|||
|
|||
Щоб оголосити тіло форми, потрібно явно використовувати `Form`, оскільки без нього параметри будуть інтерпретуватися як параметри запиту або тіла (JSON). |
|||
|
|||
/// |
|||
|
|||
## Про "поля форми" |
|||
|
|||
HTML-форми (`<form></form>`) надсилають дані на сервер у "спеціальному" кодуванні, яке відрізняється від JSON. |
|||
|
|||
**FastAPI** подбає про те, щоб зчитати ці дані з правильного місця, а не з JSON. |
|||
|
|||
/// note | Технічні деталі |
|||
|
|||
Дані з форм зазвичай кодуються за допомогою "типу медіа" `application/x-www-form-urlencoded`. |
|||
|
|||
Але якщо форма містить файли, вона кодується як `multipart/form-data`. Ви дізнаєтеся про обробку файлів у наступному розділі. |
|||
|
|||
Якщо Ви хочете дізнатися більше про ці кодування та поля форм, зверніться до <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" class="external-link" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> вебдокументації для <code>POST</code></a>. |
|||
|
|||
/// |
|||
|
|||
/// warning | Попередження |
|||
|
|||
Ви можете оголосити кілька параметрів `Form` в *операції шляху*, але не можете одночасно оголосити поля `Body`, які Ви очікуєте отримати у форматі JSON, оскільки тіло запиту буде закодовано у форматі `application/x-www-form-urlencoded`, а не `application/json`. |
|||
|
|||
Це не обмеження **FastAPI**, а частина HTTP-протоколу. |
|||
|
|||
/// |
|||
|
|||
## Підсумок |
|||
|
|||
Використовуйте `Form` для оголошення вхідних параметрів у вигляді даних форми. |
@ -0,0 +1,240 @@ |
|||
# Тестування |
|||
|
|||
Тестування **FastAPI** додатків є простим та ефективним завдяки бібліотеці <a href="https://www.starlette.io/testclient/" class="external-link" target="_blank">Starlette</a>, яка базується на <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX</a>. |
|||
Оскільки HTTPX розроблений на основі Requests, його API є інтуїтивно зрозумілим для тих, хто вже знайомий з Requests. |
|||
|
|||
З його допомогою Ви можете використовувати <a href="https://docs.pytest.org/" class="external-link" target="_blank">pytest</a> безпосередньо з **FastAPI**. |
|||
|
|||
## Використання `TestClient` |
|||
|
|||
/// info | Інформація |
|||
|
|||
Щоб використовувати `TestClient`, спочатку встановіть <a href="https://www.python-httpx.org" class="external-link" target="_blank">`httpx`</a>. |
|||
|
|||
Переконайтеся, що Ви створили [віртуальне середовище](../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`) можна знайти в <a href="https://www.python-httpx.org" class="external-link" target="_blank">документації HTTPX</a>. |
|||
|
|||
/// info | Інформація |
|||
|
|||
Зверніть увагу, що `TestClient` отримує дані, які можна конвертувати в JSON, а не Pydantic-моделі. |
|||
Якщо у Вас є Pydantic-модель у тесті, і Ви хочете передати її дані в додаток під час тестування, Ви можете використати `jsonable_encoder`, описаний у розділі [JSON Compatible Encoder](encoder.md){.internal-link target=_blank}. |
|||
|
|||
/// |
|||
|
|||
## Запуск тестів |
|||
|
|||
Після цього вам потрібно встановити `pytest`. |
|||
|
|||
Переконайтеся, що Ви створили [віртуальне середовище]{.internal-link target=_blank}, активували його і встановили необхідні пакети, наприклад: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ pip install pytest |
|||
|
|||
---> 100% |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
`pytest` автоматично знайде файли з тестами, виконає їх і надасть вам результати. |
|||
|
|||
Запустіть тести за допомогою: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```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 <span style="color: green; white-space: pre;">...... [100%]</span> |
|||
|
|||
<span style="color: green;">================= 1 passed in 0.03s =================</span> |
|||
``` |
|||
|
|||
</div> |
@ -0,0 +1,17 @@ |
|||
# Triển khai FastAPI trên các Dịch vụ Cloud |
|||
|
|||
Bạn có thể sử dụng **bất kỳ nhà cung cấp dịch vụ cloud** nào để triển khai ứng dụng FastAPI của mình. |
|||
|
|||
Trong hầu hết các trường hợp, các nhà cung cấp dịch vụ cloud lớn đều có hướng dẫn triển khai FastAPI với họ. |
|||
|
|||
## Nhà cung cấp dịch vụ Cloud - Nhà tài trợ |
|||
Một vài nhà cung cấp dịch vụ cloud ✨ [**tài trợ cho FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, điều này giúp đảm bảo sự phát triển liên tục và khỏe mạnh của FastAPI và hệ sinh thái của nó. |
|||
|
|||
Thêm nữa, điều này cũng thể hiện cam kết thực sự của họ đối với FastAPI và **cộng đồng người dùng** (bạn), vì họ không chỉ muốn cung cấp cho bạn một **dịch vụ tốt** mà còn muốn đảm bảo rằng bạn có một **framework tốt và bền vững**, đó chính là FastAPI. 🙇 |
|||
|
|||
Bạn có thể thử các dịch vụ của họ và làm theo hướng dẫn của họ: |
|||
|
|||
* <a href="https://docs.platform.sh/languages/python.html?utm_source=fastapi-signup&utm_medium=banner&utm_campaign=FastAPI-signup-June-2023" class="external-link" target="_blank">Platform.sh</a> |
|||
* <a href="https://docs.porter.run/language-specific-guides/fastapi" class="external-link" target="_blank">Porter</a> |
|||
* <a href="https://www.withcoherence.com/?utm_medium=advertising&utm_source=fastapi&utm_campaign=website" class="external-link" target="_blank">Coherence</a> |
|||
* <a href="https://docs.render.com/deploy-fastapi?utm_source=deploydoc&utm_medium=referral&utm_campaign=fastapi" class="external-link" target="_blank">Render</a> |
@ -0,0 +1,21 @@ |
|||
# Triển khai |
|||
|
|||
Triển khai một ứng dụng **FastAPI** khá dễ dàng. |
|||
|
|||
## Triển khai là gì |
|||
|
|||
Triển khai một ứng dụng có nghĩa là thực hiện các bước cần thiết để làm cho nó **sẵn sàng phục vụ người dùng**. |
|||
|
|||
Đối với một **API web**, điều này có nghĩa là đặt nó trong một **máy chủ từ xa**, với một **chương trình máy chủ** cung cấp hiệu suất tốt, ổn định, v.v., để người dùng của bạn có thể truy cập ứng dụng của bạn một cách hiệu quả và không bị gián đoạn hoặc gặp vấn đề. |
|||
|
|||
Điều này trái ngược với các **giai đoạn phát triển**, trong đó bạn liên tục thay đổi mã, phá vỡ nó và sửa nó, ngừng và khởi động lại máy chủ phát triển, v.v. |
|||
|
|||
## Các Chiến lược Triển khai |
|||
|
|||
Có nhiều cách để triển khai ứng dụng của bạn tùy thuộc vào trường hợp sử dụng của bạn và các công cụ mà bạn sử dụng. |
|||
|
|||
Bạn có thể **triển khai một máy chủ** của riêng bạn bằng cách sử dụng một sự kết hợp các công cụ, hoặc bạn có thể sử dụng một **dịch vụ cloud** để làm một số công việc cho bạn, hoặc các tùy chọn khác. |
|||
|
|||
Tôi sẽ chỉ ra một số khái niệm chính cần thiết khi triển khai một ứng dụng **FastAPI** (mặc dù hầu hết nó áp dụng cho bất kỳ loại ứng dụng web nào). |
|||
|
|||
Bạn sẽ thấy nhiều chi tiết cần thiết và một số kỹ thuật để triển khai trong các phần tiếp theo. ✨ |
@ -0,0 +1,93 @@ |
|||
# Về các phiên bản của FastAPI |
|||
|
|||
**FastAPI** đã được sử dụng ở quy mô thực tế (production) trong nhiều ứng dụng và hệ thống. Và phạm vi kiểm thử được giữ ở mức 100%. Nhưng việc phát triển của nó vẫn đang diễn ra nhanh chóng. |
|||
|
|||
Các tính năng mới được bổ sung thường xuyên, lỗi được sửa định kỳ, và mã nguồn vẫn đang được cải thiện liên tục |
|||
|
|||
Đó là lí do các phiên bản hiện tại vẫn còn là 0.x.x, điều này phản ánh rằng mỗi phiên bản có thể có các thay đổi gây mất tương thích. Điều này tuân theo các quy ước về <a href="https://semver.org/" class="external-link" target="blank">Semantic Versioning</a>. |
|||
|
|||
Bạn có thể tạo ra sản phẩm thực tế với **FastAPI** ngay bây giờ (và bạn có thể đã làm điều này trong một thời gian dài), bạn chỉ cần đảm bảo rằng bạn sử dụng một phiên bản hoạt động đúng với các đoạn mã còn lại của bạn. |
|||
|
|||
## Cố định phiên bản của `fastapi` |
|||
|
|||
Điều đầu tiên bạn nên làm là "cố định" phiên bản của **FastAPI** bạn đang sử dụng để phiên bản mới nhất mà bạn biết hoạt động đúng với ứng dụng của bạn. |
|||
|
|||
Ví dụ, giả sử bạn đang sử dụng phiên bản `0.112.0` trong ứng dụng của bạn. |
|||
|
|||
Nếu bạn sử dụng một tệp `requirements.txt` bạn có thể chỉ định phiên bản với: |
|||
|
|||
```txt |
|||
fastapi[standard]==0.112.0 |
|||
``` |
|||
|
|||
Như vậy, bạn sẽ sử dụng chính xác phiên bản `0.112.0`. |
|||
|
|||
Hoặc bạn cũng có thể cố định nó với: |
|||
|
|||
```txt |
|||
fastapi[standard]>=0.112.0,<0.113.0 |
|||
``` |
|||
|
|||
Như vậy, bạn sẽ sử dụng các phiên bản `0.112.0` trở lên, nhưng nhỏ hơn `0.113.0`, ví dụ, một phiên bản `0.112.2` vẫn được chấp nhận. |
|||
|
|||
Nếu bạn sử dụng bất kỳ công cụ nào để quản lý cài đặt của bạn, như `uv`, Poetry, Pipenv, hoặc bất kỳ công cụ nào khác, chúng đều có một cách để bạn có thể định nghĩa các phiên bản cụ thể cho các gói của bạn. |
|||
|
|||
## Các phiên bản có sẵn |
|||
|
|||
Bạn có thể xem các phiên bản có sẵn (ví dụ để kiểm tra phiên bản mới nhất) trong [Release Notes](../release-notes.md){.internal-link target=_blank}. |
|||
|
|||
## Về các phiên bản |
|||
|
|||
Theo quy ước về Semantic Versioning, bất kỳ phiên bản nào bên dưới `1.0.0` có thể thêm các thay đổi gây mất tương thích. |
|||
|
|||
**FastAPI** cũng theo quy ước rằng bất kỳ thay đổi phiên bản "PATCH" nào là cho các lỗi và các thay đổi không gây mất tương thích. |
|||
|
|||
/// tip |
|||
|
|||
"PATCH" là số cuối cùng, ví dụ, trong `0.2.3`, phiên bản PATCH là `3`. |
|||
|
|||
/// |
|||
|
|||
Vì vậy, bạn có thể cố định đến một phiên bản như: |
|||
|
|||
```txt |
|||
fastapi>=0.45.0,<0.46.0 |
|||
``` |
|||
|
|||
Các thay đổi gây mất tương thích và các tính năng mới được thêm vào trong các phiên bản "MINOR". |
|||
|
|||
/// tip |
|||
|
|||
"MINOR" là số ở giữa, ví dụ, trong `0.2.3`, phiên bản MINOR là `2`. |
|||
|
|||
/// |
|||
|
|||
## Nâng cấp các phiên bản của FastAPI |
|||
|
|||
Bạn nên thêm các bài kiểm tra (tests) cho ứng dụng của bạn. |
|||
|
|||
Với **FastAPI** điều này rất dễ dàng (nhờ vào Starlette), kiểm tra tài liệu: [Testing](../tutorial/testing.md){.internal-link target=_blank} |
|||
|
|||
Sau khi bạn có các bài kiểm tra, bạn có thể nâng cấp phiên bản **FastAPI** lên một phiên bản mới hơn, và đảm bảo rằng tất cả mã của bạn hoạt động đúng bằng cách chạy các bài kiểm tra của bạn. |
|||
|
|||
Nếu mọi thứ đang hoạt động, hoặc sau khi bạn thực hiện các thay đổi cần thiết, và tất cả các bài kiểm tra của bạn đều đi qua, thì bạn có thể cố định phiên bản của `fastapi` đến phiên bản mới hơn. |
|||
|
|||
## Về Starlette |
|||
|
|||
Bạn không nên cố định phiên bản của `starlette`. |
|||
|
|||
Các phiên bản khác nhau của **FastAPI** sẽ sử dụng một phiên bản Starlette mới hơn. |
|||
|
|||
Vì vậy, bạn có thể để **FastAPI** sử dụng phiên bản Starlette phù hợp. |
|||
|
|||
## Về Pydantic |
|||
|
|||
Pydantic bao gồm các bài kiểm tra của riêng nó cho **FastAPI**, vì vậy các phiên bản mới hơn của Pydantic (trên `1.0.0`) luôn tương thích với **FastAPI**. |
|||
|
|||
Bạn có thể cố định Pydantic đến bất kỳ phiên bản nào trên `1.0.0` mà bạn muốn. |
|||
|
|||
Ví dụ: |
|||
|
|||
```txt |
|||
pydantic>=2.7.0,<3.0.0 |
|||
``` |
@ -0,0 +1,31 @@ |
|||
import random |
|||
from typing import Union |
|||
|
|||
from fastapi import FastAPI |
|||
from pydantic import AfterValidator |
|||
from typing_extensions import Annotated |
|||
|
|||
app = FastAPI() |
|||
|
|||
data = { |
|||
"isbn-9781529046137": "The Hitchhiker's Guide to the Galaxy", |
|||
"imdb-tt0371724": "The Hitchhiker's Guide to the Galaxy", |
|||
"isbn-9781439512982": "Isaac Asimov: The Complete Stories, Vol. 2", |
|||
} |
|||
|
|||
|
|||
def check_valid_id(id: str): |
|||
if not id.startswith(("isbn-", "imdb-")): |
|||
raise ValueError('Invalid ID format, it must start with "isbn-" or "imdb-"') |
|||
return id |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items( |
|||
id: Annotated[Union[str, None], AfterValidator(check_valid_id)] = None, |
|||
): |
|||
if id: |
|||
item = data.get(id) |
|||
else: |
|||
id, item = random.choice(list(data.items())) |
|||
return {"id": id, "name": item} |
@ -0,0 +1,30 @@ |
|||
import random |
|||
from typing import Annotated |
|||
|
|||
from fastapi import FastAPI |
|||
from pydantic import AfterValidator |
|||
|
|||
app = FastAPI() |
|||
|
|||
data = { |
|||
"isbn-9781529046137": "The Hitchhiker's Guide to the Galaxy", |
|||
"imdb-tt0371724": "The Hitchhiker's Guide to the Galaxy", |
|||
"isbn-9781439512982": "Isaac Asimov: The Complete Stories, Vol. 2", |
|||
} |
|||
|
|||
|
|||
def check_valid_id(id: str): |
|||
if not id.startswith(("isbn-", "imdb-")): |
|||
raise ValueError('Invalid ID format, it must start with "isbn-" or "imdb-"') |
|||
return id |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items( |
|||
id: Annotated[str | None, AfterValidator(check_valid_id)] = None, |
|||
): |
|||
if id: |
|||
item = data.get(id) |
|||
else: |
|||
id, item = random.choice(list(data.items())) |
|||
return {"id": id, "name": item} |
@ -0,0 +1,30 @@ |
|||
import random |
|||
from typing import Annotated, Union |
|||
|
|||
from fastapi import FastAPI |
|||
from pydantic import AfterValidator |
|||
|
|||
app = FastAPI() |
|||
|
|||
data = { |
|||
"isbn-9781529046137": "The Hitchhiker's Guide to the Galaxy", |
|||
"imdb-tt0371724": "The Hitchhiker's Guide to the Galaxy", |
|||
"isbn-9781439512982": "Isaac Asimov: The Complete Stories, Vol. 2", |
|||
} |
|||
|
|||
|
|||
def check_valid_id(id: str): |
|||
if not id.startswith(("isbn-", "imdb-")): |
|||
raise ValueError('Invalid ID format, it must start with "isbn-" or "imdb-"') |
|||
return id |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items( |
|||
id: Annotated[Union[str, None], AfterValidator(check_valid_id)] = None, |
|||
): |
|||
if id: |
|||
item = data.get(id) |
|||
else: |
|||
id, item = random.choice(list(data.items())) |
|||
return {"id": id, "name": item} |
@ -1,4 +1,4 @@ |
|||
# For mkdocstrings and tests |
|||
httpx >=0.23.0,<0.28.0 |
|||
# For linting and generating docs versions |
|||
ruff ==0.6.4 |
|||
ruff ==0.9.4 |
|||
|
@ -0,0 +1,143 @@ |
|||
import importlib |
|||
|
|||
import pytest |
|||
from dirty_equals import IsStr |
|||
from fastapi.testclient import TestClient |
|||
from inline_snapshot import snapshot |
|||
|
|||
from ...utils import needs_py39, needs_py310, needs_pydanticv2 |
|||
|
|||
|
|||
@pytest.fixture( |
|||
name="client", |
|||
params=[ |
|||
pytest.param("tutorial015_an", marks=needs_pydanticv2), |
|||
pytest.param("tutorial015_an_py310", marks=(needs_py310, needs_pydanticv2)), |
|||
pytest.param("tutorial015_an_py39", marks=(needs_py39, needs_pydanticv2)), |
|||
], |
|||
) |
|||
def get_client(request: pytest.FixtureRequest): |
|||
mod = importlib.import_module( |
|||
f"docs_src.query_params_str_validations.{request.param}" |
|||
) |
|||
|
|||
client = TestClient(mod.app) |
|||
return client |
|||
|
|||
|
|||
def test_get_random_item(client: TestClient): |
|||
response = client.get("/items") |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == {"id": IsStr(), "name": IsStr()} |
|||
|
|||
|
|||
def test_get_item(client: TestClient): |
|||
response = client.get("/items?id=isbn-9781529046137") |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == { |
|||
"id": "isbn-9781529046137", |
|||
"name": "The Hitchhiker's Guide to the Galaxy", |
|||
} |
|||
|
|||
|
|||
def test_get_item_does_not_exist(client: TestClient): |
|||
response = client.get("/items?id=isbn-nope") |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == {"id": "isbn-nope", "name": None} |
|||
|
|||
|
|||
def test_get_invalid_item(client: TestClient): |
|||
response = client.get("/items?id=wtf-yes") |
|||
assert response.status_code == 422, response.text |
|||
assert response.json() == snapshot( |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"type": "value_error", |
|||
"loc": ["query", "id"], |
|||
"msg": 'Value error, Invalid ID format, it must start with "isbn-" or "imdb-"', |
|||
"input": "wtf-yes", |
|||
"ctx": {"error": {}}, |
|||
} |
|||
] |
|||
} |
|||
) |
|||
|
|||
|
|||
def test_openapi_schema(client: TestClient): |
|||
response = client.get("/openapi.json") |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == snapshot( |
|||
{ |
|||
"openapi": "3.1.0", |
|||
"info": {"title": "FastAPI", "version": "0.1.0"}, |
|||
"paths": { |
|||
"/items/": { |
|||
"get": { |
|||
"summary": "Read Items", |
|||
"operationId": "read_items_items__get", |
|||
"parameters": [ |
|||
{ |
|||
"name": "id", |
|||
"in": "query", |
|||
"required": False, |
|||
"schema": { |
|||
"anyOf": [{"type": "string"}, {"type": "null"}], |
|||
"title": "Id", |
|||
}, |
|||
} |
|||
], |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": {"application/json": {"schema": {}}}, |
|||
}, |
|||
"422": { |
|||
"description": "Validation Error", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/HTTPValidationError" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
} |
|||
}, |
|||
"components": { |
|||
"schemas": { |
|||
"HTTPValidationError": { |
|||
"properties": { |
|||
"detail": { |
|||
"items": { |
|||
"$ref": "#/components/schemas/ValidationError" |
|||
}, |
|||
"type": "array", |
|||
"title": "Detail", |
|||
} |
|||
}, |
|||
"type": "object", |
|||
"title": "HTTPValidationError", |
|||
}, |
|||
"ValidationError": { |
|||
"properties": { |
|||
"loc": { |
|||
"items": { |
|||
"anyOf": [{"type": "string"}, {"type": "integer"}] |
|||
}, |
|||
"type": "array", |
|||
"title": "Location", |
|||
}, |
|||
"msg": {"type": "string", "title": "Message"}, |
|||
"type": {"type": "string", "title": "Error Type"}, |
|||
}, |
|||
"type": "object", |
|||
"required": ["loc", "msg", "type"], |
|||
"title": "ValidationError", |
|||
}, |
|||
} |
|||
}, |
|||
} |
|||
) |
Loading…
Reference in new issue