committed by
GitHub
36 changed files with 4070 additions and 0 deletions
@ -0,0 +1,247 @@ |
|||
# Дополнительные ответы в OpenAPI |
|||
|
|||
/// warning | Предупреждение |
|||
|
|||
Это достаточно сложная тема. |
|||
|
|||
Если вы только начинаете работать с **FastAPI**, вам это может не понадобиться. |
|||
|
|||
/// |
|||
|
|||
Вы можете объявлять дополнительные ответы с дополнительными статус-кодами, типами содержимого, описаниями и т.д. |
|||
|
|||
Эти дополнительные ответы будут включены в схему OpenAPI, так что они также появятся в документации API. |
|||
|
|||
Но для этих дополнительных ответов вы должны убедиться, что возвращаете `Response`, например, `JSONResponse` напрямую, с вашим статус-кодом и содержимым. |
|||
|
|||
## Дополнительный ответ с `model` |
|||
|
|||
Вы можете передать своим декораторам *операций пути* параметр `responses`. |
|||
|
|||
Он принимает `dict`: ключами являются статус-коды для каждого ответа (например, `200`), а значениями являются другие `dict` с информацией для каждого из них. |
|||
|
|||
Каждый из этих `dict` ответа может содержать ключ `model`, содержащий Pydantic-модель, как и `response_model`. |
|||
|
|||
**FastAPI** возьмет эту модель, сгенерирует ее JSON Schema и включит в нужное место в OpenAPI. |
|||
|
|||
Например, чтобы объявить другой ответ с кодом состояния `404` и Pydantic-моделью `Message`, вы можете написать: |
|||
|
|||
{* ../../docs_src/additional_responses/tutorial001.py hl[18,22] *} |
|||
|
|||
/// note | Заметка |
|||
|
|||
Имейте в виду, что вы должны вернуть `JSONResponse` напрямую. |
|||
|
|||
/// |
|||
|
|||
/// info | Информация |
|||
|
|||
Ключ `model` не является частью OpenAPI. |
|||
|
|||
**FastAPI** возьмет Pydantic-модель оттуда, сгенерирует JSON Schema и поместит в нужное место. |
|||
|
|||
Правильное место: |
|||
|
|||
* В ключе `content`, который имеет значение другой JSON объект (`dict`), содержащий: |
|||
* Ключ с типом содержимого, например `application/json`, который в качестве значения содержит другой JSON объект, содержащий: |
|||
* Ключ `schema`, который имеет в качестве значения JSON Schema из модели, вот куда нужно поместить. |
|||
* **FastAPI** добавляет сюда ссылку на глобальные JSON Schemas в другом месте вашего OpenAPI вместо того, чтобы включать его напрямую. Таким образом, другие приложения и клиенты могут использовать эти JSON Schemas напрямую, предоставлять лучшие инструменты генерации кода и другие функции. |
|||
|
|||
/// |
|||
|
|||
Сгенерированные ответы в OpenAPI для этой *операции пути* будут: |
|||
|
|||
```JSON hl_lines="3-12" |
|||
{ |
|||
"responses": { |
|||
"404": { |
|||
"description": "Дополнительный ответ", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/Message" |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"200": { |
|||
"description": "Успешный ответ", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/Item" |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"422": { |
|||
"description": "Ошибка валидации", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/HTTPValidationError" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
Схемы ссылаются на другое место внутри схемы OpenAPI: |
|||
|
|||
```JSON hl_lines="4-16" |
|||
{ |
|||
"components": { |
|||
"schemas": { |
|||
"Message": { |
|||
"title": "Сообщение", |
|||
"required": [ |
|||
"сообщение" |
|||
], |
|||
"type": "object", |
|||
"properties": { |
|||
"сообщение": { |
|||
"title": "Сообщение", |
|||
"type": "string" |
|||
} |
|||
} |
|||
}, |
|||
"Item": { |
|||
"title": "Предмет", |
|||
"required": [ |
|||
"идентификатор", |
|||
"значение" |
|||
], |
|||
"type": "object", |
|||
"properties": { |
|||
"идентификатор": { |
|||
"title": "Идентификатор", |
|||
"type": "string" |
|||
}, |
|||
"значение": { |
|||
"title": "Значение", |
|||
"type": "string" |
|||
} |
|||
} |
|||
}, |
|||
"ValidationError": { |
|||
"title": "Ошибка валидации", |
|||
"required": [ |
|||
"местоположение", |
|||
"сообщение", |
|||
"тип" |
|||
], |
|||
"type": "object", |
|||
"properties": { |
|||
"местоположение": { |
|||
"title": "Location", |
|||
"type": "array", |
|||
"items": { |
|||
"type": "string" |
|||
} |
|||
}, |
|||
"сообщение": { |
|||
"title": "Сообщение", |
|||
"type": "string" |
|||
}, |
|||
"тип": { |
|||
"title": "Тип ошибки", |
|||
"type": "string" |
|||
} |
|||
} |
|||
}, |
|||
"HTTPValidationError": { |
|||
"title": "Ошибка валидации HTTP", |
|||
"type": "object", |
|||
"properties": { |
|||
"детали": { |
|||
"title": "Детали", |
|||
"type": "array", |
|||
"items": { |
|||
"$ref": "#/components/schemas/ValidationError" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## Дополнительные типы содержимого для основного ответа |
|||
|
|||
Вы можете использовать этот же параметр `responses`, чтобы добавить разные типы содержимого для одного и того же основного ответа. |
|||
|
|||
Например, вы можете добавить дополнительный медиа-тип `image/png`, объявив, что ваши *операции пути* могут возвращать JSON объект (с медиа-типом `application/json`) или изображение PNG: |
|||
|
|||
{* ../../docs_src/additional_responses/tutorial002.py hl[19:24,28] *} |
|||
|
|||
/// note | Заметка |
|||
|
|||
Обратите внимание, что вы должны возвращать изображение, используя `FileResponse` напрямую. |
|||
|
|||
/// |
|||
|
|||
/// info | Информация |
|||
|
|||
Если вы явно не указываете другой тип содержимого в вашем параметре `responses`, FastAPI предположит, что ответ имеет тот же тип содержимого, что и основной класс ответа (по умолчанию `application/json`). |
|||
|
|||
Но если вы задали пользовательский класс ответа с `None` в качестве типа содержимого, FastAPI будет использовать `application/json` для любого дополнительного ответа, у которого есть ассоциированная модель. |
|||
|
|||
/// |
|||
|
|||
## Комбинирование информации |
|||
|
|||
Вы также можете комбинировать информацию о ответах из разных мест, включая параметры `response_model`, `status_code` и `responses`. |
|||
|
|||
Вы можете объявить `response_model`, используя стандартный статус-код `200` (или пользовательский, если необходимо), а затем объявить дополнительную информацию для этого же ответа в `responses`, напрямую в схеме OpenAPI. |
|||
|
|||
**FastAPI** сохранит дополнительную информацию из `responses` и объединит ее с JSON Schema из вашей модели. |
|||
|
|||
Например, вы можете объявить ответ с кодом состояния `404`, который использует Pydantic-модель и имеет пользовательское `description`. |
|||
|
|||
И ответ с кодом состояния `200`, который использует ваш `response_model`, но включает пользовательский `example`: |
|||
|
|||
{* ../../docs_src/additional_responses/tutorial003.py hl[20:31] *} |
|||
|
|||
Все это будет объединено и включено в ваш OpenAPI, и показано в документации API: |
|||
|
|||
<img src="/img/tutorial/additional-responses/image01.png"> |
|||
|
|||
## Комбинирование предопределенных и пользовательских ответов |
|||
|
|||
Возможно, вы захотите иметь некоторые предопределенные ответы, которые применяются ко многим *операциям пути*, но хотите объединить их с пользовательскими ответами, необходимыми для каждой *операции пути*. |
|||
|
|||
Для таких случаев вы можете использовать технику Python "распаковка" `dict` с помощью `**dict_to_unpack`: |
|||
|
|||
```Python |
|||
old_dict = { |
|||
"old key": "old value", |
|||
"second old key": "second old value", |
|||
} |
|||
new_dict = {**old_dict, "new key": "new value"} |
|||
``` |
|||
|
|||
Здесь, `new_dict` будет содержать все пары `ключ-значение` из `old_dict` плюс новую пару `ключ-значение`: |
|||
|
|||
```Python |
|||
{ |
|||
"old key": "old value", |
|||
"second old key": "second old value", |
|||
"new key": "new value", |
|||
} |
|||
``` |
|||
|
|||
Вы можете использовать эту технику для повторного использования некоторых предопределенных ответов в ваших *операциях пути* и объединить их с дополнительными пользовательскими. |
|||
|
|||
Например: |
|||
|
|||
{* ../../docs_src/additional_responses/tutorial004.py hl[13:17,26] *} |
|||
|
|||
## Больше информации об ответах OpenAPI |
|||
|
|||
Чтобы узнать, что именно можно включить в ответы, вы можете посмотреть эти разделы в спецификации OpenAPI: |
|||
|
|||
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#responses-object" class="external-link" target="_blank">OpenAPI Responses Object</a>, включает в себя `Response Object`. |
|||
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#response-object" class="external-link" target="_blank">OpenAPI Response Object</a>, вы можете включить что угодно из этого напрямую в каждый ответ внутри вашего параметра `responses`. Включая `description`, `headers`, `content` (внутри этого вы указываете разные типы медиа и JSON Schemas), и `links`. |
@ -0,0 +1,65 @@ |
|||
# Продвинутые зависимости |
|||
|
|||
## Параметризуемые зависимости |
|||
|
|||
Все зависимости, которые мы видели, являются фиксированной функцией или классом. |
|||
|
|||
Но могут быть случаи, когда вы захотите иметь возможность задавать параметры для зависимости, не объявляя множество разных функций или классов. |
|||
|
|||
Представим, что мы хотим иметь зависимость, которая проверяет, содержит ли параметр запроса `q` некоторое фиксированное содержимое. |
|||
|
|||
Но нам нужно иметь возможность параметризовать это фиксированное содержимое. |
|||
|
|||
## "Вызываемый" экземпляр |
|||
|
|||
В Python есть способ сделать экземпляр класса "вызываемым". |
|||
|
|||
Не сам класс (он уже может быть вызываемым), а экземпляр этого класса. |
|||
|
|||
Для этого мы объявляем метод `__call__`: |
|||
|
|||
{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[12] *} |
|||
|
|||
В этом случае **FastAPI** будет использовать этот `__call__` для проверки дополнительных параметров и под-зависимостей, и он будет вызываться для передачи значения в параметр вашей *функции-обработчика пути* позже. |
|||
|
|||
## Параметризация экземпляра |
|||
|
|||
Теперь мы можем использовать `__init__`, чтобы объявить параметры экземпляра, которые мы можем использовать для "параметризации" зависимости: |
|||
|
|||
{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[9] *} |
|||
|
|||
В данном случае **FastAPI** никогда не будет касаться или заботиться об `__init__`, мы будем использовать его напрямую в нашем коде. |
|||
|
|||
## Создание экземпляра |
|||
|
|||
Мы можем создать экземпляр этого класса с помощью: |
|||
|
|||
{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[18] *} |
|||
|
|||
Таким образом, мы можем "параметризовать" нашу зависимость, в которой теперь есть `"bar"`, как атрибут `checker.fixed_content`. |
|||
|
|||
## Использование экземпляра в качестве зависимости |
|||
|
|||
Затем мы могли бы использовать этот `checker` в `Depends(checker)`, вместо `Depends(FixedContentQueryChecker)`, потому что зависимость — это экземпляр `checker`, а не сам класс. |
|||
|
|||
И при разрешении зависимости, **FastAPI** вызовет этот `checker` так: |
|||
|
|||
```Python |
|||
checker(q="somequery") |
|||
``` |
|||
|
|||
...и передаст то, что это возвращает, как значение зависимости в нашей *функции-обработчике пути* как параметр `fixed_content_included`: |
|||
|
|||
{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[22] *} |
|||
|
|||
/// tip | Совет |
|||
|
|||
Все это может показаться надуманным. И пока не совсем ясно, насколько это полезно. |
|||
|
|||
Эти примеры намеренно просты, но показывают, как все работает. |
|||
|
|||
В главах о безопасности есть утилиты-функции, которые реализованы таким же образом. |
|||
|
|||
Если вы все это поняли, вы уже знаете, как работают эти утилиты для безопасности изнутри. |
|||
|
|||
/// |
@ -0,0 +1,361 @@ |
|||
# За прокси-сервером |
|||
|
|||
В некоторых ситуациях вам может понадобиться использовать **прокси**-сервер, такой как Traefik или Nginx, с конфигурацией, добавляющей дополнительный префикс к path, который ваше приложение не видит. |
|||
|
|||
В таких случаях вы можете использовать `root_path` для настройки вашего приложения. |
|||
|
|||
`root_path` — это механизм, предоставляемый спецификацией ASGI (на основе которой построен FastAPI через Starlette). |
|||
|
|||
`root_path` используется для обработки этих конкретных случаев. |
|||
|
|||
Он также используется внутренне при монтировании подприложений. |
|||
|
|||
## Прокси с вырезанным префиксом path |
|||
|
|||
Использование прокси с вырезанным префиксом path означает, что вы можете объявить путь как `/app` в своем коде. Но затем вы добавляете слой сверху (прокси), который помещает ваше приложение **FastAPI** под путь, такой как `/api/v1`. |
|||
|
|||
В этом случае оригинальный путь `/app` будет фактически работать как `/api/v1/app`. |
|||
|
|||
Несмотря на то, что весь ваш код написан с учетом использования только `/app`. |
|||
|
|||
{* ../../docs_src/behind_a_proxy/tutorial001.py hl[6] *} |
|||
|
|||
И прокси будет **"вырезать"** **префикс пути** на лету перед передачей запроса серверу приложения (вероятно, Uvicorn через CLI FastAPI), оставляя ваше приложение уверенным, что оно работает на `/app`, чтобы вам не пришлось обновлять весь код для включения префикса `/api/v1`. |
|||
|
|||
До этого момента все будет работать как обычно. |
|||
|
|||
Но затем, когда вы откроете встроенный интерфейс документации (фронтенд), он будет ожидать получить OpenAPI-схему на `/openapi.json`, вместо `/api/v1/openapi.json`. |
|||
|
|||
Поэтому фронтенд (который работает в браузере) попытается достичь `/openapi.json` и не сможет получить OpenAPI-схему. |
|||
|
|||
Поскольку у нас есть прокси с префиксом пути `/api/v1` для нашего приложения, фронтенд должен получить OpenAPI-схему на `/api/v1/openapi.json`. |
|||
|
|||
```mermaid |
|||
graph LR |
|||
|
|||
browser("Браузер") |
|||
proxy["Прокси на http://0.0.0.0:9999/api/v1/app"] |
|||
server["Сервер на http://127.0.0.1:8000/app"] |
|||
|
|||
browser --> proxy |
|||
proxy --> server |
|||
``` |
|||
|
|||
/// совет | Совет |
|||
|
|||
IP `0.0.0.0` обычно используется, чтобы указать, что программа слушает все доступные IP-адреса на этой машине/сервере. |
|||
|
|||
/// |
|||
|
|||
Интерфейс документации также будет нуждаться в OpenAPI-схеме для декларации, что этот API `server` расположен на `/api/v1` (за прокси). Например: |
|||
|
|||
```JSON hl_lines="4-8" |
|||
{ |
|||
"openapi": "3.1.0", |
|||
// Другие данные здесь |
|||
"servers": [ |
|||
{ |
|||
"url": "/api/v1" |
|||
} |
|||
], |
|||
"paths": { |
|||
// Другие данные здесь |
|||
} |
|||
} |
|||
``` |
|||
|
|||
В этом примере "Прокси" может быть чем-то вроде **Traefik**. А сервером может быть что-то вроде CLI FastAPI с **Uvicorn**, который запускает ваше приложение FastAPI. |
|||
|
|||
### Указание `root_path` |
|||
|
|||
Чтобы достичь этого, вы можете использовать командную строку с опцией `--root-path`, например: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ fastapi run main.py --root-path /api/v1 |
|||
|
|||
<span style="color: green;">INFO</span>: Uvicorn запущен на http://127.0.0.1:8000 (Нажмите CTRL+C для остановки) |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
Если вы используете Hypercorn, у него также есть опция `--root-path`. |
|||
|
|||
/// note | Технические подробности |
|||
|
|||
Спецификация ASGI определяет `root_path` для этого случая использования. |
|||
|
|||
И опция командной строки `--root-path` предоставляет этот `root_path`. |
|||
|
|||
/// |
|||
|
|||
### Проверка текущего `root_path` |
|||
|
|||
Вы можете получить текущий `root_path`, используемый вашим приложением для каждого запроса, он является частью словаря `scope` (это часть спецификации ASGI). |
|||
|
|||
Здесь мы включаем его в сообщение просто для демонстрации. |
|||
|
|||
{* ../../docs_src/behind_a_proxy/tutorial001.py hl[8] *} |
|||
|
|||
Затем, если вы запустите Uvicorn с: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ fastapi run main.py --root-path /api/v1 |
|||
|
|||
<span style="color: green;">INFO</span>: Uvicorn запущен на http://127.0.0.1:8000 (Нажмите CTRL+C для остановки) |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
Ответ будет что-то вроде: |
|||
|
|||
```JSON |
|||
{ |
|||
"message": "Hello World", |
|||
"root_path": "/api/v1" |
|||
} |
|||
``` |
|||
|
|||
### Установка `root_path` в приложении FastAPI |
|||
|
|||
Альтернативно, если у вас нет возможности предоставить опцию командной строки, такую как `--root-path` или аналогичную, вы можете установить параметр `root_path` при создании вашего приложения FastAPI: |
|||
|
|||
{* ../../docs_src/behind_a_proxy/tutorial002.py hl[3] *} |
|||
|
|||
Передача `root_path` в `FastAPI` будет эквивалентна передаче опции командной строки `--root-path` для Uvicorn или Hypercorn. |
|||
|
|||
### О `root_path` |
|||
|
|||
Учтите, что сервер (Uvicorn) не будет использовать `root_path` ни для чего, кроме передачи его приложению. |
|||
|
|||
Но если вы зайдете через браузер по адресу <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000/app</a>, вы увидите обычный ответ: |
|||
|
|||
```JSON |
|||
{ |
|||
"message": "Hello World", |
|||
"root_path": "/api/v1" |
|||
} |
|||
``` |
|||
|
|||
Итак, он не будет ожидать доступа по адресу `http://127.0.0.1:8000/api/v1/app`. |
|||
|
|||
Uvicorn будет ожидать, что прокси получит доступ к Uvicorn по адресу `http://127.0.0.1:8000/app`, а затем это будет уже ответственность прокси — добавить дополнительный префикс `/api/v1`. |
|||
|
|||
## О прокси с вырезанным префиксом path |
|||
|
|||
Имейте в виду, что прокси с вырезанным префиксом path — это всего лишь один из способов его настройки. |
|||
|
|||
Вероятно, во многих случаях по умолчанию будет то, что у прокси нет вырезанного префикса path. |
|||
|
|||
В случае, подобном этому (без вырезанного префикса path), прокси будет слушать что-то вроде `https://myawesomeapp.com`, и тогда, если браузер перейдет на `https://myawesomeapp.com/api/v1/app`, а ваш сервер (например, Uvicorn) слушает на `http://127.0.0.1:8000`, прокси (без вырезанного префикса path) получит доступ к Uvicorn на том же пути: `http://127.0.0.1:8000/api/v1/app`. |
|||
|
|||
## Тестирование локально с Traefik |
|||
|
|||
Вы можете легко провести эксперимент локально с вырезанным префиксом path, используя <a href="https://docs.traefik.io/" class="external-link" target="_blank">Traefik</a>. |
|||
|
|||
<a href="https://github.com/containous/traefik/releases" class="external-link" target="_blank">Скачайте Traefik</a>, это один исполняемый файл, вы можете извлечь сжатый файл и запустить его прямо из терминала. |
|||
|
|||
Затем создайте файл `traefik.toml` с: |
|||
|
|||
```TOML hl_lines="3" |
|||
[entryPoints] |
|||
[entryPoints.http] |
|||
address = ":9999" |
|||
|
|||
[providers] |
|||
[providers.file] |
|||
filename = "routes.toml" |
|||
``` |
|||
|
|||
Это указывает Traefik слушать на порту 9999 и использовать другой файл `routes.toml`. |
|||
|
|||
/// совет |
|||
|
|||
Мы используем порт 9999 вместо стандартного порта HTTP 80, чтобы вам не пришлось запускать это с привилегиями администратора (`sudo`). |
|||
|
|||
/// |
|||
|
|||
Теперь создайте другой файл `routes.toml`: |
|||
|
|||
```TOML hl_lines="5 12 20" |
|||
[http] |
|||
[http.middlewares] |
|||
|
|||
[http.middlewares.api-stripprefix.stripPrefix] |
|||
prefixes = ["/api/v1"] |
|||
|
|||
[http.routers] |
|||
|
|||
[http.routers.app-http] |
|||
entryPoints = ["http"] |
|||
service = "app" |
|||
rule = "PathPrefix(`/api/v1`)" |
|||
middlewares = ["api-stripprefix"] |
|||
|
|||
[http.services] |
|||
|
|||
[http.services.app] |
|||
[http.services.app.loadBalancer] |
|||
[[http.services.app.loadBalancer.servers]] |
|||
url = "http://127.0.0.1:8000" |
|||
``` |
|||
|
|||
Этот файл настраивает Traefik для использования префикса path `/api/v1`. |
|||
|
|||
А затем Traefik перенаправит свои запросы на ваш Uvicorn, запущенный на `http://127.0.0.1:8000`. |
|||
|
|||
Теперь запустите Traefik: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ ./traefik --configFile=traefik.toml |
|||
|
|||
INFO[0000] Конфигурация загружена из файла: /home/user/awesomeapi/traefik.toml |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
И теперь запустите ваше приложение, используя опцию `--root-path`: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ fastapi run main.py --root-path /api/v1 |
|||
|
|||
<span style="color: green;">INFO</span>: Uvicorn запущен на http://127.0.0.1:8000 (Нажмите CTRL+C для остановки) |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
### Проверьте ответы |
|||
|
|||
Теперь, если вы перейдете по URL с портом для Uvicorn: <a href="http://127.0.0.1:8000/app" class="external-link" target="_blank">http://127.0.0.1:8000/app</a>, вы увидите нормальный ответ: |
|||
|
|||
```JSON |
|||
{ |
|||
"message": "Hello World", |
|||
"root_path": "/api/v1" |
|||
} |
|||
``` |
|||
|
|||
/// совет | Совет |
|||
|
|||
Обратите внимание, что, хотя вы получаете доступ к нему по адресу `http://127.0.0.1:8000/app`, он отображает `root_path` как `/api/v1`, взятый из опции `--root-path`. |
|||
|
|||
/// |
|||
|
|||
А теперь откройте URL с портом для Traefik, включая префикс path: <a href="http://127.0.0.1:9999/api/v1/app" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/app</a>. |
|||
|
|||
Мы получаем такой же ответ: |
|||
|
|||
```JSON |
|||
{ |
|||
"message": "Hello World", |
|||
"root_path": "/api/v1" |
|||
} |
|||
``` |
|||
|
|||
но на этот раз по URL с префиксом path, предоставленным прокси: `/api/v1`. |
|||
|
|||
Конечно, идея здесь в том, что все должны получить доступ к приложению через прокси, так что версия с префиксом path `/api/v1` — это "правильная" версия. |
|||
|
|||
И версия без префикса path (`http://127.0.0.1:8000/app`), предоставляемая напрямую Uvicorn, будет исключительно для _прокси_ (Traefik), чтобы получить к ней доступ. |
|||
|
|||
Это демонстрирует, как прокси (Traefik) использует префикс path и как сервер (Uvicorn) использует `root_path` из опции `--root-path`. |
|||
|
|||
### Проверьте интерфейс документации |
|||
|
|||
Но вот где начинается веселье. ✨ |
|||
|
|||
"Официальный" способ доступа к приложению будет через прокси с префиксом path, который мы определили. Поэтому, как и ожидалось, если вы попытаетесь использовать интерфейс документации, обслуживаемый Uvicorn напрямую, без префикса path в URL, он не будет работать, потому что предполагается, что доступ будет осуществляться через прокси. |
|||
|
|||
Вы можете проверить это по адресу <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>: |
|||
|
|||
<img src="/img/tutorial/behind-a-proxy/image01.png"> |
|||
|
|||
Но если мы получим доступ к интерфейсу документации по "официальному" URL, используя прокси с портом `9999`, по адресу `/api/v1/docs`, он работает правильно! 🎉 |
|||
|
|||
Вы можете проверить это по адресу <a href="http://127.0.0.1:9999/api/v1/docs" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/docs</a>: |
|||
|
|||
<img src="/img/tutorial/behind-a-proxy/image02.png"> |
|||
|
|||
Как и хотели. ✔️ |
|||
|
|||
Это потому, что FastAPI использует этот `root_path` для создания `server` по умолчанию в OpenAPI с URL, предоставленным `root_path`. |
|||
|
|||
## Дополнительные серверы |
|||
|
|||
/// warning | Предупреждение |
|||
|
|||
Это более сложный случай использования. Вы можете пропустить его, если хотите. |
|||
|
|||
/// |
|||
|
|||
По умолчанию **FastAPI** создаст `server` в схеме OpenAPI с URL для `root_path`. |
|||
|
|||
Но вы также можете предоставить другие альтернативные `servers`, например, если вы хотите, чтобы *этот же* интерфейс документации взаимодействовал как со средой тестирования, так и со средой продакшн. |
|||
|
|||
Если вы передаете пользовательский список `servers` и существует `root_path` (потому что ваш API находится за прокси), **FastAPI** вставит "server" с этим `root_path` в начало списка. |
|||
|
|||
Например: |
|||
|
|||
{* ../../docs_src/behind_a_proxy/tutorial003.py hl[4:7] *} |
|||
|
|||
Создаст схему OpenAPI, как: |
|||
|
|||
```JSON hl_lines="5-7" |
|||
{ |
|||
"openapi": "3.1.0", |
|||
// Другие данные здесь |
|||
"servers": [ |
|||
{ |
|||
"url": "/api/v1" |
|||
}, |
|||
{ |
|||
"url": "https://stag.example.com", |
|||
"description": "Тестовая среда" |
|||
}, |
|||
{ |
|||
"url": "https://prod.example.com", |
|||
"description": "Продакшн среда" |
|||
} |
|||
], |
|||
"paths": { |
|||
// Другие данные здесь |
|||
} |
|||
} |
|||
``` |
|||
|
|||
/// совет | Совет |
|||
|
|||
Обратите внимание на автоматически сгенерированный сервер с `url` значением `/api/v1`, взятым из `root_path`. |
|||
|
|||
/// |
|||
|
|||
В интерфейсе документации по адресу <a href="http://127.0.0.1:9999/api/v1/docs" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/docs</a> это будет выглядеть так: |
|||
|
|||
<img src="/img/tutorial/behind-a-proxy/image03.png"> |
|||
|
|||
/// совет | Совет |
|||
|
|||
Интерфейс документации будет взаимодействовать с сервером, который вы выберете. |
|||
|
|||
/// |
|||
|
|||
### Отключение автоматического сервера от `root_path` |
|||
|
|||
Если вы не хотите, чтобы **FastAPI** включал автоматический сервер, используя `root_path`, вы можете использовать параметр `root_path_in_servers=False`: |
|||
|
|||
{* ../../docs_src/behind_a_proxy/tutorial004.py hl[9] *} |
|||
|
|||
и тогда он не будет включен в схему OpenAPI. |
|||
|
|||
## Монтирование подприложения |
|||
|
|||
Если вам нужно смонтировать подприложение (как описано в [Подприложения - Монтирование](sub-applications.md){.internal-link target=_blank}), используя также прокси с `root_path`, вы можете сделать это обычным образом, как и ожидалось. |
|||
|
|||
FastAPI будет использовать `root_path` умно, поэтому все будет работать, как и задумано. ✨ |
@ -0,0 +1,313 @@ |
|||
# Пользовательский ответ - HTML, поток, файл и другие |
|||
|
|||
По умолчанию **FastAPI** будет возвращать ответы, используя `JSONResponse`. |
|||
|
|||
Вы можете переопределить это, возвращая `Response` напрямую, как показано в разделе [Возврат Response напрямую](response-directly.md){.internal-link target=_blank}. |
|||
|
|||
Но если вы возвращаете `Response` напрямую (или любой подкласс, такой как `JSONResponse`), данные не будут автоматически конвертироваться (даже если вы объявите `response_model`), и документация не будет автоматически генерироваться (например, включая конкретный "тип содержимого", в HTTP-заголовке `Content-Type` как часть генерируемого OpenAPI). |
|||
|
|||
Но вы также можете объявить `Response`, который хотите использовать (например, любой подкласс `Response`), в *декораторе операции пути*, используя параметр `response_class`. |
|||
|
|||
Содержимое, которое вы возвращаете из вашей *функции-обработчика пути*, будет помещено внутрь этого `Response`. |
|||
|
|||
И если этот `Response` имеет JSON тип содержимого (`application/json`), как в случае с `JSONResponse` и `UJSONResponse`, данные, которые вы возвращаете, будут автоматически конвертироваться (и фильтроваться) с помощью любого Pydantic `response_model`, который вы объявили в *декораторе операции пути*. |
|||
|
|||
/// note | Замечание |
|||
|
|||
Если вы используете класс ответа без типа содержимого, FastAPI ожидает, что ваш ответ не будет содержать контент, поэтому он не задокументирует формат ответа в автоматически генерируемой документации OpenAPI. |
|||
|
|||
/// |
|||
|
|||
## Использование `ORJSONResponse` |
|||
|
|||
Например, если вы заботитесь о производительности, вы можете установить и использовать <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a> и установить ответ `ORJSONResponse`. |
|||
|
|||
Импортируйте класс (подкласс) `Response`, который вы хотите использовать, и объявите его в *декораторе операции пути*. |
|||
|
|||
Для больших ответов возврат `Response` напрямую значительно быстрее, чем возврат словаря. |
|||
|
|||
Это происходит потому, что по умолчанию FastAPI будет проверять каждый элемент внутри и убеждаться, что он сериализуем как JSON, используя тот же [Совместимый с JSON кодировщик](../tutorial/encoder.md){.internal-link target=_blank}, о котором рассказано в руководстве. Это позволяет вам возвращать **произвольные объекты**, например, модели базы данных. |
|||
|
|||
Но если вы уверены, что содержимое, которое вы возвращаете, **сериализуемо с JSON**, вы можете передать его напрямую в класс ответа и избежать дополнительной нагрузки, которую FastAPI имел бы, передавая возвращаемый контент через `jsonable_encoder` перед передачей его в класс ответа. |
|||
|
|||
{* ../../docs_src/custom_response/tutorial001b.py hl[2,7] *} |
|||
|
|||
/// info | Информация |
|||
|
|||
Параметр `response_class` также будет использоваться для определения "типа содержимого" ответа. |
|||
|
|||
В данном случае HTTP-заголовок `Content-Type` будет установлен в `application/json`. |
|||
|
|||
И он будет задокументирован как таковой в OpenAPI. |
|||
|
|||
/// |
|||
|
|||
/// tip | Совет |
|||
|
|||
`ORJSONResponse` доступен только в FastAPI, не в Starlette. |
|||
|
|||
/// |
|||
|
|||
## HTML-ответ |
|||
|
|||
Чтобы вернуть ответ с HTML напрямую из **FastAPI**, используйте `HTMLResponse`. |
|||
|
|||
* Импортируйте `HTMLResponse`. |
|||
* Передайте `HTMLResponse` в качестве параметра `response_class` вашего *декоратора операции пути*. |
|||
|
|||
{* ../../docs_src/custom_response/tutorial002.py hl[2,7] *} |
|||
|
|||
/// info | Информация |
|||
|
|||
Параметр `response_class` также будет использоваться для определения "типа содержимого" ответа. |
|||
|
|||
В этом случае HTTP-заголовок `Content-Type` будет установлен в `text/html`. |
|||
|
|||
И он будет задокументирован как таковой в OpenAPI. |
|||
|
|||
/// |
|||
|
|||
### Возврат `Response` |
|||
|
|||
Как показано в разделе [Возврат Response напрямую](response-directly.md){.internal-link target=_blank}, вы также можете переопределить ответ напрямую в вашей *операции пути*, возвращая его. |
|||
|
|||
Тот же пример, что и выше, возвращающий `HTMLResponse`, может выглядеть так: |
|||
|
|||
{* ../../docs_src/custom_response/tutorial003.py hl[2,7,19] *} |
|||
|
|||
/// warning | Предупреждение |
|||
|
|||
`Response`, возвращенный напрямую вашей *функцией-обработчиком пути*, не будет задокументирован в OpenAPI (например, `Content-Type` не будет задокументирован) и не будет виден в автоматически генерируемой интерактивной документации. |
|||
|
|||
/// |
|||
|
|||
/// info | Информация |
|||
|
|||
Конечно, фактический заголовок `Content-Type`, статус-код и др., будут из объекта `Response`, который вы вернули. |
|||
|
|||
/// |
|||
|
|||
### Документирование в OpenAPI и переопределение `Response` |
|||
|
|||
Если вы хотите переопределить ответ из функции, но при этом документация должна содержать "тип содержимого" в OpenAPI, вы можете использовать параметр `response_class` И вернуть объект `Response`. |
|||
|
|||
Тогда `response_class` будет использоваться только для документирования OpenAPI *операции пути*, но ваш `Response` будет использоваться как есть. |
|||
|
|||
#### Возврат `HTMLResponse` напрямую |
|||
|
|||
Например, это может быть что-то вроде: |
|||
|
|||
{* ../../docs_src/custom_response/tutorial004.py hl[7,21,23] *} |
|||
|
|||
В этом примере функция `generate_html_response()` уже генерирует и возвращает `Response` вместо возврата HTML в виде `str`. |
|||
|
|||
Возвращая результат вызова `generate_html_response()`, вы уже возвращаете `Response`, который переопределит поведение **FastAPI** по умолчанию. |
|||
|
|||
Но так как вы также передали `HTMLResponse` в `response_class`, **FastAPI** будет знать, как задокументировать это в OpenAPI и интерактивной документации как HTML с `text/html`: |
|||
|
|||
<img src="/img/tutorial/custom-response/image01.png"> |
|||
|
|||
## Доступные ответы |
|||
|
|||
Вот некоторые из доступных ответов. |
|||
|
|||
Имейте в виду, что вы можете использовать `Response` для возврата чего-либо еще или даже создать собственный подкласс. |
|||
|
|||
/// note | Технические детали |
|||
|
|||
Вы также можете использовать `from starlette.responses import HTMLResponse`. |
|||
|
|||
**FastAPI** предоставляет те же `starlette.responses`, что и `fastapi.responses`, просто для вашего удобства, как разработчика. Но большинство доступных ответов поступают напрямую из Starlette. |
|||
|
|||
/// |
|||
|
|||
### `Response` |
|||
|
|||
Основной класс `Response`, все остальные ответы наследуются от него. |
|||
|
|||
Вы можете вернуть его напрямую. |
|||
|
|||
Он принимает следующие параметры: |
|||
|
|||
* `content` - `str` или `bytes`. |
|||
* `status_code` - `int` HTTP статус-код. |
|||
* `headers` - `dict` строк. |
|||
* `media_type` - `str`, который задает тип содержимого, например `"text/html"`. |
|||
|
|||
FastAPI (фактически Starlette) автоматически добавит заголовок Content-Length. Он также включит заголовок Content-Type, основанный на `media_type` и добавит кодировку для текстовых типов. |
|||
|
|||
{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *} |
|||
|
|||
### `HTMLResponse` |
|||
|
|||
Принимает текст или байты и возвращает HTML-ответ, как вы прочли выше. |
|||
|
|||
### `PlainTextResponse` |
|||
|
|||
Принимает текст или байты и возвращает ответ с простым текстом. |
|||
|
|||
{* ../../docs_src/custom_response/tutorial005.py hl[2,7,9] *} |
|||
|
|||
### `JSONResponse` |
|||
|
|||
Принимает данные и возвращает ответ, кодированный как `application/json`. |
|||
|
|||
Это ответ по умолчанию, используемый в **FastAPI**, как вы прочли выше. |
|||
|
|||
### `ORJSONResponse` |
|||
|
|||
Быстрая альтернатива JSON-ответа, использующая <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a>, как вы прочли выше. |
|||
|
|||
/// info | Информация |
|||
|
|||
Для этого требуется установка `orjson`, например с помощью `pip install orjson`. |
|||
|
|||
/// |
|||
|
|||
### `UJSONResponse` |
|||
|
|||
Альтернативный JSON-ответ, использующий <a href="https://github.com/ultrajson/ultrajson" class="external-link" target="_blank">`ujson`</a>. |
|||
|
|||
/// info | Информация |
|||
|
|||
Для этого требуется установка `ujson`, например с помощью `pip install ujson`. |
|||
|
|||
/// |
|||
|
|||
/// warning | Предупреждение |
|||
|
|||
`ujson` менее осторожна, чем встроенная реализация Python, в том, как она обрабатывает некоторые крайние случаи. |
|||
|
|||
/// |
|||
|
|||
{* ../../docs_src/custom_response/tutorial001.py hl[2,7] *} |
|||
|
|||
/// tip | Совет |
|||
|
|||
Возможно, `ORJSONResponse` может быть более быстрой альтернативой. |
|||
|
|||
/// |
|||
|
|||
### `RedirectResponse` |
|||
|
|||
Возвращает HTTP-перенаправление. Использует статус-код 307 (Временное перенаправление) по умолчанию. |
|||
|
|||
Вы можете вернуть `RedirectResponse` напрямую: |
|||
|
|||
{* ../../docs_src/custom_response/tutorial006.py hl[2,9] *} |
|||
|
|||
--- |
|||
|
|||
Или вы можете использовать его в параметре `response_class`: |
|||
|
|||
|
|||
{* ../../docs_src/custom_response/tutorial006b.py hl[2,7,9] *} |
|||
|
|||
Если вы сделаете это, то можете вернуть URL непосредственно из вашей *функции-обработчика пути*. |
|||
|
|||
В этом случае, используемый `status_code` будет тем, что по умолчанию для `RedirectResponse`, а именно `307`. |
|||
|
|||
--- |
|||
|
|||
Вы также можете использовать параметр `status_code` в сочетании с параметром `response_class`: |
|||
|
|||
{* ../../docs_src/custom_response/tutorial006c.py hl[2,7,9] *} |
|||
|
|||
### `StreamingResponse` |
|||
|
|||
Принимает асинхронный генератор или обычный генератор/итератор и отправляет тело ответа в потоке. |
|||
|
|||
{* ../../docs_src/custom_response/tutorial007.py hl[2,14] *} |
|||
|
|||
#### Использование `StreamingResponse` с объектами, похожими на файлы |
|||
|
|||
Если у вас есть объект, похожий на файл (например, объект, возвращаемый `open()`), вы можете создать функцию-генератор для итерации по этому объекту. |
|||
|
|||
Таким образом, вам не нужно предварительно загружать все в память, и вы можете передать эту функцию-генератор в `StreamingResponse` и вернуть ее. |
|||
|
|||
Это включает множество библиотек для взаимодействия с облачным хранилищем, обработки видео и других. |
|||
|
|||
{* ../../docs_src/custom_response/tutorial008.py hl[2,10:12,14] *} |
|||
|
|||
1. Это функция-генератор. Это "функция-генератор", потому что она содержит оператор `yield`. |
|||
2. Используя блок `with`, мы обеспечиваем закрытие объекта, похожего на файл, после завершения выполнения функции-генератора. То есть после того, как отправка ответа завершена. |
|||
3. Этот `yield from` указывает функции итерироваться по объекту `file_like`. Затем для каждой части, которую итерация проходит, возвращать эту часть из функции-генератора (`iterfile`). |
|||
|
|||
Таким образом, это функция-генератор, которая передает работу по "генерации" чему-то еще во внутренности. |
|||
|
|||
Делая так, мы можем поместить это в блок `with` и таким образом обеспечить закрытие объекта, похожего на файл, после завершения. |
|||
|
|||
/// tip | Совет |
|||
|
|||
Обратите внимание, что здесь, так как мы используем стандартный `open()`, который не поддерживает `async` и `await`, мы объявляем операцию пути с использованием обычного `def`. |
|||
|
|||
/// |
|||
|
|||
### `FileResponse` |
|||
|
|||
Асинхронно отправляет файл в поток как ответ. |
|||
|
|||
Принимает другой набор аргументов для создания экземпляра, чем другие типы ответов: |
|||
|
|||
* `path` - Путь к файлу, который необходимо отправить в поток. |
|||
* `headers` - Любые пользовательские заголовки, которые необходимо включить, в виде словаря. |
|||
* `media_type` - Строка, задающая тип содержимого. Если не установлено, для определения типа содержимого будет использоваться имя файла или путь. |
|||
* `filename` - Если установлено, это будет включено в заголовок `Content-Disposition` ответа. |
|||
|
|||
Ответы на файлы будут включать соответствующие заголовки `Content-Length`, `Last-Modified` и `ETag`. |
|||
|
|||
{* ../../docs_src/custom_response/tutorial009.py hl[2,10] *} |
|||
|
|||
Вы также можете использовать параметр `response_class`: |
|||
|
|||
{* ../../docs_src/custom_response/tutorial009b.py hl[2,8,10] *} |
|||
|
|||
В этом случае, вы можете вернуть путь к файлу напрямую из вашей *функции-обработчика пути*. |
|||
|
|||
## Класс пользовательского ответа |
|||
|
|||
Вы можете создать свой собственный класс пользовательского ответа, наследуя от `Response` и используя его. |
|||
|
|||
Например, предположим, вы хотите использовать <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a>, но с некоторыми пользовательскими настройками, не используемыми в включенном классе `ORJSONResponse`. |
|||
|
|||
Допустим, вы хотите, чтобы он возвращал JSON с отступами и форматированием, поэтому вы хотите использовать опцию orjson `orjson.OPT_INDENT_2`. |
|||
|
|||
Вы могли бы создать `CustomORJSONResponse`. Главное, что вам нужно сделать, это создать метод `Response.render(content)`, который возвращает содержимое как `bytes`: |
|||
|
|||
{* ../../docs_src/custom_response/tutorial009c.py hl[9:14,17] *} |
|||
|
|||
Теперь вместо возвращения: |
|||
|
|||
```json |
|||
{"message": "Hello World"} |
|||
``` |
|||
|
|||
...этот ответ вернет: |
|||
|
|||
```json |
|||
{ |
|||
"message": "Hello World" |
|||
} |
|||
``` |
|||
|
|||
Конечно, вы, вероятно, найдете гораздо лучшие способы использовать это, чем форматирование JSON. 😉 |
|||
|
|||
## Класс ответа по умолчанию |
|||
|
|||
При создании экземпляра класса **FastAPI** или `APIRouter` вы можете указать, какой класс ответа использовать по умолчанию. |
|||
|
|||
Параметр, который это определяет, называется `default_response_class`. |
|||
|
|||
В приведенном ниже примере **FastAPI** будет использовать `ORJSONResponse` по умолчанию во всех *операциях пути* вместо `JSONResponse`. |
|||
|
|||
{* ../../docs_src/custom_response/tutorial010.py hl[2,4] *} |
|||
|
|||
/// tip | Совет |
|||
|
|||
Вы все еще можете переопределять `response_class` в *операциях пути*, как и раньше. |
|||
|
|||
/// |
|||
|
|||
## Дополнительная документация |
|||
|
|||
Вы также можете объявить тип содержимого и многие другие детали в OpenAPI, используя `responses`: [Дополнительные ответы в OpenAPI](additional-responses.md){.internal-link target=_blank}. |
@ -0,0 +1,95 @@ |
|||
# Использование Dataclasses |
|||
|
|||
FastAPI построен на основе **Pydantic**, и я уже показывал вам, как использовать модели Pydantic для объявления HTTP-запросов и HTTP-ответов. |
|||
|
|||
Но FastAPI также поддерживает использование <a href="https://docs.python.org/3/library/dataclasses.html" class="external-link" target="_blank">`dataclasses`</a> таким же образом: |
|||
|
|||
{* ../../docs_src/dataclasses/tutorial001.py hl[1,7:12,19:20] *} |
|||
|
|||
Это по-прежнему поддерживается благодаря **Pydantic**, так как в нем есть <a href="https://docs.pydantic.dev/latest/concepts/dataclasses/#use-of-stdlib-dataclasses-with-basemodel" class="external-link" target="_blank">внутренняя поддержка `dataclasses`</a>. |
|||
|
|||
Итак, даже с приведенным выше кодом, который явно не использует Pydantic, FastAPI использует Pydantic для преобразования стандартных dataclasses в собственный вариант dataclasses от Pydantic. |
|||
|
|||
И, конечно же, он поддерживает то же самое: |
|||
|
|||
* валидация данных |
|||
* сериализация данных |
|||
* документация данных и т.д. |
|||
|
|||
Это работает так же, как и с моделями Pydantic. И это фактически достигается таким же образом, под капотом, с использованием Pydantic. |
|||
|
|||
/// info | Информация |
|||
|
|||
Имейте в виду, что dataclasses не могут выполнять все то, что могут делать модели Pydantic. |
|||
|
|||
Поэтому, возможно, вам все равно придется использовать модели Pydantic. |
|||
|
|||
Но если у вас есть много dataclasses, это отличный трюк, чтобы использовать их для создания веб-API с помощью FastAPI. 🤓 |
|||
|
|||
/// |
|||
|
|||
## Dataclasses в `response_model` |
|||
|
|||
Вы также можете использовать `dataclasses` в параметре `response_model`: |
|||
|
|||
{* ../../docs_src/dataclasses/tutorial002.py hl[1,7:13,19] *} |
|||
|
|||
Dataclass будет автоматически преобразован в Pydantic dataclass. |
|||
|
|||
Таким образом, его схема будет отображаться в интерфейсе документации API: |
|||
|
|||
<img src="/img/tutorial/dataclasses/image01.png"> |
|||
|
|||
## Dataclasses в Вложенных Структурах Данных |
|||
|
|||
Вы также можете комбинировать `dataclasses` с другими аннотациями типов для создания вложенных структур данных. |
|||
|
|||
В некоторых случаях, возможно, вам все равно придется использовать версию `dataclasses` от Pydantic. Например, если у вас возникли ошибки с автоматической генерацией документации API. |
|||
|
|||
В этом случае вы можете просто заменить стандартные `dataclasses` на `pydantic.dataclasses`, что является заменой "на лету": |
|||
|
|||
{* ../../docs_src/dataclasses/tutorial003.py hl[1,5,8:11,14:17,23:25,28] *} |
|||
|
|||
1. Мы по-прежнему импортируем `field` из стандартных `dataclasses`. |
|||
|
|||
2. `pydantic.dataclasses` является заменой "на лету" для `dataclasses`. |
|||
|
|||
3. Dataclass `Author` включает список dataclasses `Item`. |
|||
|
|||
4. Dataclass `Author` используется как параметр `response_model`. |
|||
|
|||
5. Вы можете использовать другие стандартные аннотации типов с dataclasses как HTTP-тело запроса. |
|||
|
|||
В этом случае это список dataclasses `Item`. |
|||
|
|||
6. Здесь мы возвращаем словарь, содержащий `items`, который является списком dataclasses. |
|||
|
|||
FastAPI по-прежнему способен <abbr title="конвертирование данных в формат, который может быть передан">сериализовать</abbr> данные в JSON. |
|||
|
|||
7. Здесь `response_model` использует аннотацию типа список dataclasses `Author`. |
|||
|
|||
Снова вы можете комбинировать `dataclasses` с стандартными аннотациями типов. |
|||
|
|||
8. Обратите внимание, что эта *функция-обработчик пути* использует обычный `def` вместо `async def`. |
|||
|
|||
Как всегда, в FastAPI вы можете сочетать `def` и `async def` в зависимости от необходимости. |
|||
|
|||
Если вы забыли, когда использовать какой, посмотрите раздел _"Нет времени?"_ в документации о [`async` и `await`](../async.md#in-a-hurry){.internal-link target=_blank}. |
|||
|
|||
9. Эта *функция-обработчик пути* не возвращает dataclasses (хотя могла бы), а список словарей с внутренними данными. |
|||
|
|||
FastAPI будет использовать параметр `response_model` (который включает dataclasses), чтобы преобразовать ответ. |
|||
|
|||
Вы можете комбинировать `dataclasses` с другими аннотациями типов в различных комбинациях, чтобы формировать сложные структуры данных. |
|||
|
|||
Посмотрите советы по аннотации в коде выше, чтобы узнать больше деталей. |
|||
|
|||
## Узнать больше |
|||
|
|||
Вы также можете комбинировать `dataclasses` с другими моделями Pydantic, наследовать их, включать их в свои собственные модели и т.д. |
|||
|
|||
Чтобы узнать больше, ознакомьтесь с <a href="https://docs.pydantic.dev/latest/concepts/dataclasses/" class="external-link" target="_blank">документацией Pydantic о dataclasses</a>. |
|||
|
|||
## Версия |
|||
|
|||
Это доступно начиная с версии FastAPI `0.67.0`. 🔖 |
@ -0,0 +1,165 @@ |
|||
# События жизненного цикла |
|||
|
|||
Вы можете определить логику (код), которая должна быть выполнена перед запуском приложения. Это значит, что данный код будет выполнен **единожды**, **перед** тем, как приложение **начнет принимать HTTP-запросы**. |
|||
|
|||
Аналогично, вы можете определить логику (код), которая должна быть выполнена при остановке приложения. В этом случае код будет выполнен **единожды**, **после** обработки, возможно, **множества HTTP-запросов**. |
|||
|
|||
Поскольку этот код выполняется до того, как приложение **начинает** принимать запросы, и непосредственно после того, как оно **заканчивает** обработку запросов, он охватывает весь **lifespan** приложения (слово "lifespan" сейчас станет важным 😉). |
|||
|
|||
Это может быть очень полезно для настройки **ресурсов**, которые вам нужно использовать для всего приложения, и которые **разделяются** между запросами, и/или которые требуют **освобождения** после завершения работы. Например, пул подключений к базе данных или загрузка общей модели машинного обучения. |
|||
|
|||
## Пример использования |
|||
|
|||
Давайте начнем с примера **использования**, а затем посмотрим, как его решить. |
|||
|
|||
Предположим, у вас есть несколько **моделей машинного обучения**, которые вы хотите использовать для обработки запросов. 🤖 |
|||
|
|||
Те же самые модели разделяются между всеми запросами, то есть это не одна модель на запрос или одна на пользователя или что-то подобное. |
|||
|
|||
Представим, что загрузка модели может **занять значительное время**, потому что необходимо прочитать много **данных с диска**. Поэтому вы не хотите делать это для каждого запроса. |
|||
|
|||
Вы можете загрузить ее на верхнем уровне модуля/файла, но это также означало бы, что модель **загрузится**, даже если вы просто запускаете простой автоматизированный тест, и такой тест был бы **медленный**, так как пришлось бы ждать, пока модель загрузится, прежде чем можно было бы выполнить независимую часть кода. |
|||
|
|||
Это то, что мы решаем: давайте загрузим модель до того, как запросы начнут обрабатываться, но только непосредственно перед тем, как приложение начнет принимать запросы, а не в процессе загрузки кода. |
|||
|
|||
## Lifespan |
|||
|
|||
Вы можете определить эту логику *запуска* и *выключения* с помощью параметра `lifespan` в приложении `FastAPI` и "менеджера контекста" (я покажу вам, что это такое, через мгновение). |
|||
|
|||
Давайте начнем с примера и затем разберем его подробно. |
|||
|
|||
Создаем асинхронную функцию `lifespan()` с `yield` следующим образом: |
|||
|
|||
{* ../../docs_src/events/tutorial003.py hl[16,19] *} |
|||
|
|||
Здесь мы симулируем дорогостоящую операцию *запуска*, загружая модель, помещая (фальшивую) функцию модели в словарь с моделями машинного обучения перед `yield`. Этот код будет выполнен **до** того, как приложение **начнет принимать запросы**, в процессе *запуска*. |
|||
|
|||
И затем, сразу после `yield`, мы выгружаем модель. Этот код будет выполнен **после** того, как приложение **завершит обработку запросов**, непосредственно перед *выключением*. Это может, например, освободить такие ресурсы, как память или GPU. |
|||
|
|||
/// tip | Совет |
|||
|
|||
Выключение происходит, когда вы **останавливаете** приложение. |
|||
|
|||
Возможно, вам нужно запустить новую версию, или вы просто устали от его работы. 🤷 |
|||
|
|||
/// |
|||
|
|||
### Функция Lifespan |
|||
|
|||
Первое, что следует заметить, это то, что мы определяем асинхронную функцию с `yield`. Это очень похоже на зависимости с `yield`. |
|||
|
|||
{* ../../docs_src/events/tutorial003.py hl[14:19] *} |
|||
|
|||
Первая часть функции, до `yield`, будет выполнена **до** того, как приложение начнет свою работу. |
|||
|
|||
А часть после `yield` будет выполнена **после** завершения работы приложения. |
|||
|
|||
### Асинхронный менеджер контекста |
|||
|
|||
Если вы посмотрите, функция декорирована с помощью `@asynccontextmanager`. |
|||
|
|||
Это преобразует функцию в нечто, называемое "**асинхронным менеджером контекста**". |
|||
|
|||
{* ../../docs_src/events/tutorial003.py hl[1,13] *} |
|||
|
|||
**Менеджер контекста** в Python - это нечто, что вы можете использовать в операторе `with`, например, `open()` может быть использован как менеджер контекста: |
|||
|
|||
```Python |
|||
with open("file.txt") as file: |
|||
file.read() |
|||
``` |
|||
|
|||
В последних версиях Python также существует **асинхронный менеджер контекста**. Вы бы использовали его с `async with`: |
|||
|
|||
```Python |
|||
async with lifespan(app): |
|||
await do_stuff() |
|||
``` |
|||
|
|||
Когда вы создаете менеджер контекста или асинхронный менеджер контекста, как выше, его работа заключается в том, что, до входа в блок `with`, он выполняет код перед `yield`, и после выхода из блока `with`, он выполняет код после `yield`. |
|||
|
|||
В нашем примере кода выше мы не используем его напрямую, но передаем его FastAPI для использования. |
|||
|
|||
Параметр `lifespan` приложения `FastAPI` принимает **асинхронный менеджер контекста**, так что мы можем передать ему наш новый асинхронный менеджер контекста `lifespan`. |
|||
|
|||
{* ../../docs_src/events/tutorial003.py hl[22] *} |
|||
|
|||
## Альтернативные события (устаревшие) |
|||
|
|||
/// warning | Предупреждение |
|||
|
|||
Рекомендуемый способ обработки *запуска* и *выключения* — это использование параметра `lifespan` приложения `FastAPI`, как описано выше. Если вы предоставите параметр `lifespan`, обработчики событий `startup` и `shutdown` больше не будут вызываться. Вы можете использовать либо `lifespan`, либо события, но не оба. |
|||
|
|||
Эту часть можно, вероятно, пропустить. |
|||
|
|||
/// |
|||
|
|||
Существует альтернативный способ определения логики, которая будет выполнена во время *запуска* и во время *выключения*. |
|||
|
|||
Вы можете определить обработчики событий (функции), которые необходимо выполнить до запуска приложения или при его остановке. |
|||
|
|||
Эти функции могут быть объявлены как `async def`, так и обычный `def`. |
|||
|
|||
### Событие `startup` |
|||
|
|||
Чтобы добавить функцию, которую следует запустить перед запуском приложения, объявите ее с событием `"startup"`: |
|||
|
|||
{* ../../docs_src/events/tutorial001.py hl[8] *} |
|||
|
|||
В этом случае функция-обработчик события `startup` инициализирует "базу данных" элементов (просто `dict`) с некоторыми значениями. |
|||
|
|||
Вы можете добавить более одной функции-обработчика события. |
|||
|
|||
И ваше приложение не начнет принимать запросы до тех пор, пока все обработчики событий `startup` не будут выполнены. |
|||
|
|||
### Событие `shutdown` |
|||
|
|||
Чтобы добавить функцию, которую следует запустить при остановке приложения, объявите ее с событием `"shutdown"`: |
|||
|
|||
{* ../../docs_src/events/tutorial002.py hl[6] *} |
|||
|
|||
Здесь функция-обработчик события `shutdown` запишет строку текста `"Application shutdown"` в файл `log.txt`. |
|||
|
|||
/// info | Информация |
|||
|
|||
В функции `open()`, `mode="a"` означает "добавить", так что строка будет добавлена после того, что уже есть в этом файле, без перезаписи предыдущего содержимого. |
|||
|
|||
/// |
|||
|
|||
/// tip | Совет |
|||
|
|||
Обратите внимание, что в данном случае мы используем стандартную функцию Python `open()`, которая взаимодействует с файлом. |
|||
|
|||
Таким образом, это включает в себя ввод/вывод (I/O), который требует "ожидания" для записи данных на диск. |
|||
|
|||
Но `open()` не использует `async` и `await`. |
|||
|
|||
Поэтому мы объявляем функцию-обработчик события как стандартный `def` вместо `async def`. |
|||
|
|||
/// |
|||
|
|||
### `startup` и `shutdown` вместе |
|||
|
|||
Существует большая вероятность, что логика ваших *запуска* и *выключения* связана; возможно, вы хотите что-то запустить, а затем завершить, получить ресурс, а затем освободить его и т. д. |
|||
|
|||
Делать это в раздельных функциях, которые не используют совместно логику или переменные, сложнее, так как придется сохранять значения в глобальных переменных или использовать подобные уловки. |
|||
|
|||
Из-за этого теперь рекомендуется вместо этого использовать `lifespan`, как объяснено выше. |
|||
|
|||
## Технические детали |
|||
|
|||
Просто техническая деталь для любознательных гиков. 🤓 |
|||
|
|||
В основе, в технической спецификации ASGI, это часть <a href="https://asgi.readthedocs.io/en/latest/specs/lifespan.html" class="external-link" target="_blank">Протокола Lifespan</a>, и он определяет события, называемые `startup` и `shutdown`. |
|||
|
|||
/// info | Информация |
|||
|
|||
Вы можете прочитать больше об обработчиках `lifespan` в Starlette в <a href="https://www.starlette.io/lifespan/" class="external-link" target="_blank">документации "Lifespan" Starlette</a>. |
|||
|
|||
Включая информацию о том, как обрабатывать состояние lifespan, которое может быть использовано в других частях вашего кода. |
|||
|
|||
/// |
|||
|
|||
## Подприложения |
|||
|
|||
🚨 Помните, что эти события жизненного цикла (запуска и завершения) будут выполнены только для основного приложения, а не для [подприложений - монтировок](sub-applications.md){.internal-link target=_blank}. |
@ -0,0 +1,261 @@ |
|||
# Генерация клиентов |
|||
|
|||
Так как **FastAPI** основан на спецификации OpenAPI, вы автоматически получаете совместимость со многими инструментами, включая автоматическую документацию API (предоставляемую Swagger UI). |
|||
|
|||
Один конкретный плюс, который не всегда очевиден, заключается в том, что вы можете **сгенерировать клиентов** (иногда их называют <abbr title="Software Development Kits">**SDKs**</abbr> ) для вашего API для многих разных **языков программирования**. |
|||
|
|||
## Генераторы клиентов OpenAPI |
|||
|
|||
Существует множество инструментов для генерации клиентов из **OpenAPI**. |
|||
|
|||
Один из популярных инструментов — это <a href="https://openapi-generator.tech/" class="external-link" target="_blank">OpenAPI Generator</a>. |
|||
|
|||
Если вы создаете **фронтенд**, очень интересной альтернативой является <a href="https://github.com/hey-api/openapi-ts" class="external-link" target="_blank">openapi-ts</a>. |
|||
|
|||
## Генераторы клиентов и SDK - спонсоры |
|||
|
|||
Существуют также некоторые **поддерживаемые компаниями** генераторы клиентов и SDK, основанные на OpenAPI (FastAPI), в некоторых случаях они могут предложить вам **дополнительные функции** поверх высококачественных сгенерированных SDK/клиентов. |
|||
|
|||
Некоторые из них также ✨ [**спонсируют FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, что обеспечивает продолжительное и здоровое **развитие** FastAPI и его **экосистемы**. |
|||
|
|||
Это также демонстрирует их истинную приверженность FastAPI и его **сообществу** (вам), ведь они не только хотят предоставить вам **качественное обслуживание**, но и стремятся обеспечить вам **качественный и здоровый фреймворк**, FastAPI. 🙇 |
|||
|
|||
Например, вам может быть интересно попробовать: |
|||
|
|||
* <a href="https://speakeasy.com/editor?utm_source=fastapi+repo&utm_medium=github+sponsorship" class="external-link" target="_blank">Speakeasy</a> |
|||
* <a href="https://www.stainlessapi.com/?utm_source=fastapi&utm_medium=referral" class="external-link" target="_blank">Stainless</a> |
|||
* <a href="https://developers.liblab.com/tutorials/sdk-for-fastapi?utm_source=fastapi" class="external-link" target="_blank">liblab</a> |
|||
|
|||
Также существует несколько других компаний, предлагающих аналогичные услуги, которые вы можете найти в интернете. 🤓 |
|||
|
|||
## Генерация TypeScript-клиента для фронтенда |
|||
|
|||
Давайте начнем с простой FastAPI-программы: |
|||
|
|||
{* ../../docs_src/generate_clients/tutorial001_py39.py hl[7:9,12:13,16:17,21] *} |
|||
|
|||
Обратите внимание, что *операции пути* определяют модели, которые они используют для полезной нагрузки запроса и ответа, используя модели `Item` и `ResponseMessage`. |
|||
|
|||
### Документация API |
|||
|
|||
Если вы перейдете к документации API, вы увидите, что в ней содержатся **схемы** для данных, которые должны быть отправлены в запросах и получены в ответах: |
|||
|
|||
<img src="/img/tutorial/generate-clients/image01.png"> |
|||
|
|||
Вы видите эти схемы, потому что они были объявлены с моделями в приложении. |
|||
|
|||
Эта информация доступна в **схеме OpenAPI** приложения и затем отображается в документации API (с помощью Swagger UI). |
|||
|
|||
И эта же информация из моделей, включенных в OpenAPI, может быть использована для **генерации кода клиента**. |
|||
|
|||
### Генерация TypeScript-клиента |
|||
|
|||
Теперь, когда у нас есть приложение с моделями, мы можем сгенерировать код клиента для фронтенда. |
|||
|
|||
#### Установка `openapi-ts` |
|||
|
|||
Вы можете установить `openapi-ts` в код своего фронтенда с помощью: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ npm install @hey-api/openapi-ts --save-dev |
|||
|
|||
---> 100% |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
#### Генерация кода клиента |
|||
|
|||
Чтобы сгенерировать код клиента, вы можете использовать командное приложение `openapi-ts`, которое теперь будет установлено. |
|||
|
|||
Поскольку оно установлено в локальном проекте, вы, вероятно, не сможете вызвать эту команду напрямую, но вы можете поместить её в свой файл `package.json`. |
|||
|
|||
Он может выглядеть следующим образом: |
|||
|
|||
```JSON hl_lines="7" |
|||
{ |
|||
"name": "frontend-app", |
|||
"version": "1.0.0", |
|||
"description": "", |
|||
"main": "index.js", |
|||
"scripts": { |
|||
"generate-client": "openapi-ts --input http://localhost:8000/openapi.json --output ./src/client --client axios" |
|||
}, |
|||
"author": "", |
|||
"license": "", |
|||
"devDependencies": { |
|||
"@hey-api/openapi-ts": "^0.27.38", |
|||
"typescript": "^4.6.2" |
|||
} |
|||
} |
|||
``` |
|||
|
|||
После добавления этого скрипта NPM `generate-client` вы можете запустить его с помощью: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ npm run generate-client |
|||
|
|||
frontend-app@1.0.0 generate-client /home/user/code/frontend-app |
|||
> openapi-ts --input http://localhost:8000/openapi.json --output ./src/client --client axios |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
Эта команда сгенерирует код в `./src/client` и будет использовать `axios` (фронтенд HTTP-библиотеку) для внутренних нужд. |
|||
|
|||
### Попробуйте код клиента |
|||
|
|||
Теперь вы можете импортировать и использовать код клиента, он может выглядеть следующим образом, обратите внимание, что вы получаете автозавершение для методов: |
|||
|
|||
<img src="/img/tutorial/generate-clients/image02.png"> |
|||
|
|||
Вы также получите автозавершение для отправляемой полезной нагрузки: |
|||
|
|||
<img src="/img/tutorial/generate-clients/image03.png"> |
|||
|
|||
/// tip | Совет |
|||
|
|||
Обратите внимание на автозавершение для `name` и `price`, которые были определены в приложении FastAPI, в модели `Item`. |
|||
|
|||
/// |
|||
|
|||
Вы получите ошибки на лету для данных, которые вы отправляете: |
|||
|
|||
<img src="/img/tutorial/generate-clients/image04.png"> |
|||
|
|||
Объект-ответ также будет иметь автозавершение: |
|||
|
|||
<img src="/img/tutorial/generate-clients/image05.png"> |
|||
|
|||
## FastAPI-приложение с тегами |
|||
|
|||
Во многих случаях ваше FastAPI-приложение будет больше, и вы, скорее всего, будете использовать теги для разделения разных групп *операций пути*. |
|||
|
|||
Например, у вас может быть раздел для **товаров** и другой раздел для **пользователей**, и они могут быть разделены с помощью тегов: |
|||
|
|||
{* ../../docs_src/generate_clients/tutorial002_py39.py hl[21,26,34] *} |
|||
|
|||
### Генерация TypeScript-клиента с тегами |
|||
|
|||
Если вы генерируете клиента для FastAPI-приложения с использованем тегов, то обычно клиентский код также будет разделен по тегам. |
|||
|
|||
Таким образом, вы сможете правильно упорядочить и сгруппировать клиентский код: |
|||
|
|||
<img src="/img/tutorial/generate-clients/image06.png"> |
|||
|
|||
В данном случае у вас есть: |
|||
|
|||
* `ItemsService` |
|||
* `UsersService` |
|||
|
|||
### Имена методов клиента |
|||
|
|||
Сейчас сгенерированные имена методов, такие как `createItemItemsPost`, не выглядят очень чистыми: |
|||
|
|||
```TypeScript |
|||
ItemsService.createItemItemsPost({name: "Plumbus", price: 5}) |
|||
``` |
|||
|
|||
...это потому, что генератор клиента использует внутренний **идентификатор операции** OpenAPI для каждой *операции пути*. |
|||
|
|||
OpenAPI требует, чтобы каждый идентификатор операции был уникальным среди всех *операций пути*, поэтому FastAPI использует **название функции**, **путь** и **метод/операцию HTTP** для генерации этого идентификатора операции, поскольку таким образом он может обеспечить уникальность идентификаторов операций. |
|||
|
|||
Но я покажу вам, как улучшить это. 🤓 |
|||
|
|||
## Пользовательские идентификаторы операций и лучшие имена методов |
|||
|
|||
Вы можете **изменить** способ **генерации** этих идентификаторов операций, чтобы упростить их и получить **более простые названия методов** в клиентах. |
|||
|
|||
В этом случае вы должны обеспечить уникальность каждого идентификатора операции другим способом. |
|||
|
|||
Например, вы можете убедиться, что каждая *операция пути* имеет тег, а затем генерировать идентификатор операции на основе **тега** и **названия операции пути** (названия функции). |
|||
|
|||
### Пользовательская функция генерации уникального идентификатора |
|||
|
|||
FastAPI использует **уникальный ID** для каждой *операции пути*, он используется для **идентификатора операции**, а также для имен любых необходимых пользовательских моделей для запросов или ответов. |
|||
|
|||
Вы можете настроить эту функцию. Она принимает `APIRoute` и возвращает строку. |
|||
|
|||
Например, здесь используется первый тег (обычно у вас будет только один тег) и название *операции пути* (название функции). |
|||
|
|||
Вы можете передать эту пользовательскую функцию **FastAPI** в параметре `generate_unique_id_function`: |
|||
|
|||
{* ../../docs_src/generate_clients/tutorial003_py39.py hl[6:7,10] *} |
|||
|
|||
### Генерация TypeScript-клиента с пользовательскими идентификаторами операций |
|||
|
|||
Теперь, если вы снова сгенерируете клиент, вы увидите, что у него улучшенные названия методов: |
|||
|
|||
<img src="/img/tutorial/generate-clients/image07.png"> |
|||
|
|||
Как видите, названия методов теперь содержат тег, а затем имя функции, теперь они не включают информацию из URL-пути и HTTP-операции. |
|||
|
|||
### Предобработка спецификации OpenAPI для генератора клиентов |
|||
|
|||
Сгенерированный код все равно содержит некоторую **дублирующую информацию**. |
|||
|
|||
Мы уже знаем, что этот метод связан с **товарами (items)**, потому что это слово появилось в `ItemsService` (взято из тега), но у нас все равно есть имя тега в имени метода. 😕 |
|||
|
|||
Вероятно, мы все равно захотим сохранить это для OpenAPI в целом, так как это обеспечит **уникальность** идентификаторов операций. |
|||
|
|||
Но для сгенерированного клиента мы можем **изменить** идентификаторы операций OpenAPI прямо перед генерацией клиентов, только чтобы сделать эти названия методов более приятными и **чистыми**. |
|||
|
|||
Мы можем загрузить OpenAPI JSON в файл `openapi.json`, а затем **удалить** этот префикс тега с помощью такого скрипта: |
|||
|
|||
{* ../../docs_src/generate_clients/tutorial004.py *} |
|||
|
|||
//// tab | Node.js |
|||
|
|||
```Javascript |
|||
{!> ../../docs_src/generate_clients/tutorial004.js!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
Таким образом, идентификаторы операций будут переименованы из `items-get_items` в просто `get_items`, так генератор клиентов сможет генерировать более простые имена методов. |
|||
|
|||
### Генерация TypeScript-клиента с предобработанным OpenAPI |
|||
|
|||
Теперь, так как конечный результат находится в файле `openapi.json`, вы измените файл `package.json`, чтобы использовать этот локальный файл, например: |
|||
|
|||
```JSON hl_lines="7" |
|||
{ |
|||
"name": "frontend-app", |
|||
"version": "1.0.0", |
|||
"description": "", |
|||
"main": "index.js", |
|||
"scripts": { |
|||
"generate-client": "openapi-ts --input ./openapi.json --output ./src/client --client axios" |
|||
}, |
|||
"author": "", |
|||
"license": "", |
|||
"devDependencies": { |
|||
"@hey-api/openapi-ts": "^0.27.38", |
|||
"typescript": "^4.6.2" |
|||
} |
|||
} |
|||
``` |
|||
|
|||
После генерации нового клиента у вас теперь будут **чистые названия методов**, с всеми **подсказками автозавершения**, **ошибками на лету** и т.д.: |
|||
|
|||
<img src="/img/tutorial/generate-clients/image08.png"> |
|||
|
|||
## Преимущества |
|||
|
|||
При использовании автоматически сгенерированных клиентов вы получите **автозавершение** для: |
|||
|
|||
* Методов. |
|||
* Полезной нагрузки запроса в теле, параметрах запроса и т.д. |
|||
* Полезной нагрузки ответа. |
|||
|
|||
Вы также получите **ошибки на лету** для всего. |
|||
|
|||
И всякий раз, когда вы обновляете код на серверной стороне и **регенерируете** фронтенд, он будет содержать любые новые *операции пути* как методы, старые будут удалены, а любые другие изменения будут отражены в сгенерированном коде. 🤓 |
|||
|
|||
Это также означает, что если что-то изменится, это будет **отражено** в клиентском коде автоматически. И если вы **соберёте** клиент, у него возникнет ошибка, если у вас возникнет **несоответствие** в используемых данных. |
|||
|
|||
Таким образом, вы **обнаружите множество ошибок** на раннем этапе цикла разработки, вместо того чтобы ждать, пока ошибки проявятся у ваших конечных пользователей в продакшне, и потом пытаться выяснить, в чем проблема. ✨ |
@ -0,0 +1,96 @@ |
|||
# Продвинутое использование Middleware |
|||
|
|||
В основном руководстве вы узнали, как добавить [пользовательский Middleware](../tutorial/middleware.md){.internal-link target=_blank} в ваше приложение. |
|||
|
|||
А также вы узнали, как обрабатывать [CORS с использованием `CORSMiddleware`](../tutorial/cors.md){.internal-link target=_blank}. |
|||
|
|||
В этом разделе мы увидим, как использовать другие middleware. |
|||
|
|||
## Добавление ASGI middleware |
|||
|
|||
Так как **FastAPI** основан на Starlette и реализует спецификацию <abbr title="Asynchronous Server Gateway Interface">ASGI</abbr>, вы можете использовать любой ASGI middleware. |
|||
|
|||
Для работы middleware не обязательно должны быть созданы специально для FastAPI или Starlette, достаточно того, чтобы они следовали спецификации ASGI. |
|||
|
|||
В общем случае, ASGI middleware представляют собой классы, которые ожидают получение ASGI приложения в качестве первого аргумента. |
|||
|
|||
Таким образом, в документации для сторонних ASGI middleware, скорее всего, будет указано сделать следующее: |
|||
|
|||
```Python |
|||
from unicorn import UnicornMiddleware |
|||
|
|||
app = SomeASGIApp() |
|||
|
|||
new_app = UnicornMiddleware(app, some_config="rainbow") |
|||
``` |
|||
|
|||
Но FastAPI (на самом деле Starlette) предоставляет более простой способ сделать это, обеспечивая правильную обработку ошибок сервера и работу пользовательских обработчиков ошибок. |
|||
|
|||
Для этого используйте `app.add_middleware()` (как в примере для CORS). |
|||
|
|||
```Python |
|||
from fastapi import FastAPI |
|||
from unicorn import UnicornMiddleware |
|||
|
|||
app = FastAPI() |
|||
|
|||
app.add_middleware(UnicornMiddleware, some_config="rainbow") |
|||
``` |
|||
|
|||
`app.add_middleware()` принимает класс middleware в качестве первого аргумента и любые дополнительные аргументы, которые должны быть переданы в middleware. |
|||
|
|||
## Интегрированные middleware |
|||
|
|||
**FastAPI** включает несколько middleware для общих случаев использования, далее мы рассмотрим, как их использовать. |
|||
|
|||
/// note | Технические детали |
|||
|
|||
В следующих примерах вы также можете использовать `from starlette.middleware.something import SomethingMiddleware`. |
|||
|
|||
**FastAPI** предоставляет несколько middleware в `fastapi.middleware` исключительно для удобства разработчика. Но большинство доступных middleware поступает непосредственно из Starlette. |
|||
|
|||
/// |
|||
|
|||
## `HTTPSRedirectMiddleware` |
|||
|
|||
Обеспечивает, чтобы все входящие запросы были перенаправлены на `https` или `wss`. |
|||
|
|||
Любой входящий запрос на `http` или `ws` будет перенаправлен на защищенную схему. |
|||
|
|||
{* ../../docs_src/advanced_middleware/tutorial001.py hl[2,6] *} |
|||
|
|||
## `TrustedHostMiddleware` |
|||
|
|||
Обеспечивает наличие корректно заданного HTTP-заголовка `Host` во всех входящих запросах для защиты от атак на основе HTTP-заголовка Host. |
|||
|
|||
{* ../../docs_src/advanced_middleware/tutorial002.py hl[2,6:8] *} |
|||
|
|||
Поддерживаются следующие аргументы: |
|||
|
|||
* `allowed_hosts` - список доменных имен, которые разрешены в качестве имен хостов. Разрешены шаблоны доменов, такие как `*.example.com`, для соответствия поддоменам. Для разрешения всех имен хостов можно использовать `allowed_hosts=["*"]` или вовсе не добавлять middleware. |
|||
|
|||
Если входящий запрос не валидируется, отправляется ответ с кодом `400`. |
|||
|
|||
## `GZipMiddleware` |
|||
|
|||
Обрабатывает GZip-ответы для любого запроса, который включает `"gzip"` в HTTP-заголовке `Accept-Encoding`. |
|||
|
|||
Middleware будет обрабатывать как стандартные, так и потоковые HTTP-ответы. |
|||
|
|||
{* ../../docs_src/advanced_middleware/tutorial003.py hl[2,6] *} |
|||
|
|||
Поддерживаются следующие аргументы: |
|||
|
|||
* `minimum_size` - не выполнять GZip сжатие для HTTP-ответов, которые меньше этого минимального размера в байтах. По умолчанию `500`. |
|||
* `compresslevel` - используется во время GZip сжатия. Это число в диапазоне от 1 до 9. По умолчанию `9`. Меньшее значение приводит к более быстрому сжатию, но большему размеру файлов, в то время как большее значение приводит к более медленному сжатию, но меньшему размеру файлов. |
|||
|
|||
## Другие middleware |
|||
|
|||
Существует много других ASGI middleware. |
|||
|
|||
Например: |
|||
|
|||
* <a href="https://github.com/encode/uvicorn/blob/master/uvicorn/middleware/proxy_headers.py" class="external-link" target="_blank">`ProxyHeadersMiddleware` от Uvicorn</a> |
|||
* <a href="https://github.com/florimondmanca/msgpack-asgi" class="external-link" target="_blank">MessagePack</a> |
|||
|
|||
Чтобы увидеть другие доступные middleware, ознакомьтесь с <a href="https://www.starlette.io/middleware/" class="external-link" target="_blank">документацией Starlette по Middleware</a> и <a href="https://github.com/florimondmanca/awesome-asgi" class="external-link" target="_blank">списком ASGI Awesome List</a>. |
@ -0,0 +1,186 @@ |
|||
# OpenAPI Callbacks |
|||
|
|||
Вы можете создать API с *операцией пути*, которая может инициировать запрос к *внешнему API*, созданному кем-то другим (вероятно, тем же разработчиком, который будет *использовать* ваше API). |
|||
|
|||
Процесс, который происходит, когда ваше API-приложение вызывает *внешний API*, называется "callback". Потому что ПО, написанное внешним разработчиком, отправляет запрос вашему API, а затем ваше API *отвечает*, отправляя запрос к *внешнему API* (который, возможно, был создан тем же разработчиком). |
|||
|
|||
В этом случае, вы могли бы захотеть задокументировать, как этот внешний API *должен* выглядеть. Какие *операции пути* он должен содержать, какое тело ожидать, какой ответ должен возвращать и т.д. |
|||
|
|||
## Приложение с callback'ами |
|||
|
|||
Рассмотрим это на примере. |
|||
|
|||
Представьте, что вы разрабатываете приложение, позволяющее создавать счета. |
|||
|
|||
Эти счета будут иметь `id`, `title` (опционально), `customer` и `total`. |
|||
|
|||
Пользователь вашего API (внешний разработчик) создаст счет в вашем API с помощью POST-запроса. |
|||
|
|||
Затем ваше API будет (давайте представим): |
|||
|
|||
* Отправлять счет какому-то клиенту внешнего разработчика. |
|||
* Собирать деньги. |
|||
* Отправлять уведомление обратно пользователю API (внешнему разработчику). |
|||
* Это будет сделано с помощью отправки POST-запроса (от *вашего API*) в некоторый *внешний API*, предоставленный этим внешним разработчиком (это и есть "callback"). |
|||
|
|||
## Обычное приложение **FastAPI** |
|||
|
|||
Сначала посмотрим, как обычное приложение API выглядело бы до добавления callback'а. |
|||
|
|||
Оно будет иметь *операцию пути*, которая будет принимать `Invoice` в теле запроса и параметр запроса `callback_url`, который будет содержать URL для callback'а. |
|||
|
|||
Эта часть довольно обычная, и, вероятно, большая часть кода уже знакома вам: |
|||
|
|||
{* ../../docs_src/openapi_callbacks/tutorial001.py hl[9:13,36:53] *} |
|||
|
|||
/// tip | Подсказка |
|||
|
|||
Параметр запроса `callback_url` использует тип Url из Pydantic <a href="https://docs.pydantic.dev/latest/api/networks/" class="external-link" target="_blank">Url</a>. |
|||
|
|||
/// |
|||
|
|||
Единственное новое здесь — это `callbacks=invoices_callback_router.routes` как аргумент к *декоратору операции пути*. Дальше мы увидим, что это значит. |
|||
|
|||
## Документирование callback'а |
|||
|
|||
Фактический код callback'а будет сильно зависеть от вашего API-приложения. |
|||
|
|||
И, вероятно, значительно изменится от одного приложения к другому. |
|||
|
|||
Это может быть всего одна или две строчки кода, такие как: |
|||
|
|||
```Python |
|||
callback_url = "https://example.com/api/v1/invoices/events/" |
|||
httpx.post(callback_url, json={"description": "Invoice paid", "paid": True}) |
|||
``` |
|||
|
|||
Но, возможно, наиболее важная часть callback'а — это убедиться, что пользователь вашего API (внешний разработчик) корректно реализует *внешний API*, согласно данным, которые *ваше API* собирается отправить в теле запроса callback'а и т.д. |
|||
|
|||
Итак, следующее, что мы сделаем, это добавим код для документации, как этот *внешний API* должен выглядеть для получения callback'а от *вашего API*. |
|||
|
|||
Эта документация отобразится в интерфейсе Swagger по адресу `/docs` в вашем API и позволит внешним разработчикам узнать, как создать *внешний API*. |
|||
|
|||
Этот пример не реализует сам callback (это может быть всего одна строка кода), только часть документации. |
|||
|
|||
/// tip | Подсказка |
|||
|
|||
Фактический callback — это просто HTTP-запрос. |
|||
|
|||
При реализации callback'а самостоятельно, вы можете использовать что-то вроде <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX</a> или <a href="https://requests.readthedocs.io/" class="external-link" target="_blank">Requests</a>. |
|||
|
|||
/// |
|||
|
|||
## Написание кода документации для callback'а |
|||
|
|||
Этот код не будет выполнен в вашем приложении, нам нужно его только чтобы *документировать*, как *внешний API* должен выглядеть. |
|||
|
|||
Но вы уже знаете, как легко создавать автоматическую документацию для API с **FastAPI**. |
|||
|
|||
Поэтому мы используем эти же знания, чтобы задокументировать, как *внешний API* должен выглядеть... создавая *операцию(ии) пути*, которые внешний API должен реализовать (те, которые ваше API вызовет). |
|||
|
|||
/// tip | Подсказка |
|||
|
|||
Когда пишете код для документации callback'а, может быть полезно представить, что вы — этот *внешний разработчик*. И что вы в данный момент реализуете *внешний API*, а не *ваше API*. |
|||
|
|||
Временное принятие этой точки зрения (внешнего разработчика) поможет вам скорее определить, куда помещать параметры, Pydantic-модель для тела запроса, для ответа и т.д. для этого *внешнего API*. |
|||
|
|||
/// |
|||
|
|||
### Создание callback `APIRouter` |
|||
|
|||
Сначала создайте новый `APIRouter`, который будет содержать один или несколько callback'ов. |
|||
|
|||
{* ../../docs_src/openapi_callbacks/tutorial001.py hl[3,25] *} |
|||
|
|||
### Создание callback *операции пути* |
|||
|
|||
Чтобы создать callback *операцию пути*, используйте тот же `APIRouter`, который вы создали выше. |
|||
|
|||
Она должна выглядеть как обычная операция пути FastAPI: |
|||
|
|||
* Она, вероятно, должна иметь объявление тела запроса, которое она должна принять, например `body: InvoiceEvent`. |
|||
* И она может также иметь объявление ответа, который она должна вернуть, например `response_model=InvoiceEventReceived`. |
|||
|
|||
{* ../../docs_src/openapi_callbacks/tutorial001.py hl[16:18,21:22,28:32] *} |
|||
|
|||
Есть 2 основных отличия от обычной *операции пути*: |
|||
|
|||
* Реальный код не нужен, так как ваше приложение никогда не вызовет этот код. Он используется только для документирования *внешнего API*. Поэтому функция может просто содержать `pass`. |
|||
* В *пути* может содержаться <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#key-expression" class="external-link" target="_blank">выражение OpenAPI 3</a> (подробнее ниже), где можно использовать переменные с параметрами и частями оригинального запроса, отправленного к *вашему API*. |
|||
|
|||
### Выражение пути callback'а |
|||
|
|||
Путь callback'а может содержать <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#key-expression" class="external-link" target="_blank">выражение OpenAPI 3</a>, которое может включать части оригинального запроса, отправленного к *вашему API*. |
|||
|
|||
В этом случае это `str`: |
|||
|
|||
```Python |
|||
"{$callback_url}/invoices/{$request.body.id}" |
|||
``` |
|||
|
|||
Таким образом, если пользователь вашего API (внешний разработчик) отправляет запрос к *вашему API* по адресу: |
|||
|
|||
``` |
|||
https://yourapi.com/invoices/?callback_url=https://www.external.org/events |
|||
``` |
|||
|
|||
с JSON-телом: |
|||
|
|||
```JSON |
|||
{ |
|||
"id": "2expen51ve", |
|||
"customer": "Mr. Richie Rich", |
|||
"total": "9999" |
|||
} |
|||
``` |
|||
|
|||
тогда *ваше API* обработает счет, и чуть позже отправит callback-запрос на `callback_url` (во *внешний API*): |
|||
|
|||
``` |
|||
https://www.external.org/events/invoices/2expen51ve |
|||
``` |
|||
|
|||
с JSON-телом, содержащим что-то вроде: |
|||
|
|||
```JSON |
|||
{ |
|||
"description": "Payment celebration", |
|||
"paid": true |
|||
} |
|||
``` |
|||
|
|||
и ожидает ответа от этого *внешнего API* с JSON-телом вроде: |
|||
|
|||
```JSON |
|||
{ |
|||
"ok": true |
|||
} |
|||
``` |
|||
|
|||
/// tip | Подсказка |
|||
|
|||
Обратите внимание, как callback URL включает URL, полученный как параметр запроса в `callback_url` (`https://www.external.org/events`), и также `id` счета из тела JSON (`2expen51ve`). |
|||
|
|||
/// |
|||
|
|||
### Добавление router для callback'ов |
|||
|
|||
На данном этапе у вас есть *операция(ии) пути для callback'а*, необходимые (те, которые *внешний разработчик* должен реализовать во *внешнем API*) в callback router, который вы создали выше. |
|||
|
|||
Теперь используйте параметр `callbacks` в *декораторе операции пути вашего API*, чтобы передать атрибут `.routes` (который на самом деле является просто `list`'ом маршрутов/*операций пути*) из этого callback router: |
|||
|
|||
{* ../../docs_src/openapi_callbacks/tutorial001.py hl[35] *} |
|||
|
|||
/// tip | Подсказка |
|||
|
|||
Обратите внимание, что вы не передаете сам router (`invoices_callback_router`) в `callback=`, а передаете атрибут `.routes`, как в `invoices_callback_router.routes`. |
|||
|
|||
/// |
|||
|
|||
### Проверка документации |
|||
|
|||
Теперь вы можете запустить ваше приложение и перейти на <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>. |
|||
|
|||
Вы увидите вашу документацию, включая раздел "Callbacks" для вашей *операции пути*, который показывает, как должен выглядеть *внешний API*: |
|||
|
|||
<img src="/img/tutorial/openapi-callbacks/image01.png"> |
@ -0,0 +1,55 @@ |
|||
# OpenAPI Webhooks |
|||
|
|||
Существуют случаи, когда вы хотите сообщить пользователям вашего API, что ваше приложение может отправлять запросы их приложениям с данными, обычно для уведомления о каком-то событии. |
|||
|
|||
Это означает, что вместо обычного процесса, когда ваши пользователи отправляют запросы вашему API, это ваш API (или ваше приложение) может отправлять запросы их системе (их API, их приложению). |
|||
|
|||
Это обычно называется **webhook**. |
|||
|
|||
## Шаги вебхуков |
|||
|
|||
Процесс обычно состоит в том, что **вы определяете** в вашем коде сообщение, которое вы собираетесь отправить, **тело запроса**. |
|||
|
|||
Вы также каким-либо образом определяете, в какие **моменты** ваше приложение будет отправлять эти запросы или события. |
|||
|
|||
И **ваши пользователи** каким-либо образом (например, в веб-панели управления) определяют **URL**, куда ваше приложение должно отправлять эти запросы. |
|||
|
|||
Вся **логика** регистрации URL-адресов для вебхуков и кода для фактической отправки этих запросов зависит от вас. Вы пишете это в **собственном коде** так, как хотите. |
|||
|
|||
## Документирование вебхуков с помощью **FastAPI** и OpenAPI |
|||
|
|||
С помощью **FastAPI** и OpenAPI вы можете определить названия этих вебхуков, типы HTTP операций, которые ваше приложение может отправлять (например, `POST`, `PUT` и т.д.), и **тела запросов**, которые ваше приложение будет отправлять. |
|||
|
|||
Это может значительно упростить вашим пользователям **внедрение их API** для получения ваших **вебхуков**, они даже могут автоматически сгенерировать часть своего API кода. |
|||
|
|||
/// info | Информация |
|||
|
|||
Вебхуки доступны в OpenAPI 3.1.0 и выше, поддерживаемые FastAPI `0.99.0` и выше. |
|||
|
|||
/// |
|||
|
|||
## Приложение с вебхуками |
|||
|
|||
Когда вы создаете приложение **FastAPI**, есть атрибут `webhooks`, который вы можете использовать для определения *вебхуков* так же, как вы бы определяли *операции пути*, например с помощью `@app.webhooks.post()`. |
|||
|
|||
{* ../../docs_src/openapi_webhooks/tutorial001.py hl[9:13,36:53] *} |
|||
|
|||
Вебхуки, которые вы определяете, попадут в **схему OpenAPI** и в **автоматическую документацию UI**. |
|||
|
|||
/// info | Информация |
|||
|
|||
Объект `app.webhooks` на самом деле является `APIRouter`, того же типа, который вы бы использовали при структурировании своего приложения с множеством файлов. |
|||
|
|||
/// |
|||
|
|||
Обратите внимание, что с вебхуками вы на самом деле не объявляете *путь* (например, `/items/`), текст, который вы передаете, является просто **идентификатором** вебхука (название события), например в `@app.webhooks.post("new-subscription")`, имя вебхука — `new-subscription`. |
|||
|
|||
Это потому, что ожидается, что **ваши пользователи** определят фактический **путь URL**, куда они хотят получать запрос webhook, каким-то другим образом (например, в веб-панели управления). |
|||
|
|||
### Проверьте документацию |
|||
|
|||
Теперь вы можете запустить ваше приложение и перейти по адресу <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>. |
|||
|
|||
Вы увидите, что ваша документация содержит обычные *операции пути* и теперь также некоторые **вебхуки**: |
|||
|
|||
<img src="/img/tutorial/openapi-webhooks/image01.png"> |
@ -0,0 +1,204 @@ |
|||
# Продвинутая конфигурация операций пути |
|||
|
|||
## OpenAPI operationId |
|||
|
|||
/// warning | Предупреждение |
|||
|
|||
Если вы не "эксперт" в OpenAPI, то, скорее всего, вам это не понадобится. |
|||
|
|||
/// |
|||
|
|||
Вы можете задать `operationId` для OpenAPI, который будет использоваться в вашей *операции пути* с помощью параметра `operation_id`. |
|||
|
|||
Следует удостовериться, что он уникален для каждой операции. |
|||
|
|||
{* ../../docs_src/path_operation_advanced_configuration/tutorial001.py hl[6] *} |
|||
|
|||
### Использование имени функции *операции пути* в качестве operationId |
|||
|
|||
Если вы хотите использовать имена ваших функций API в качестве `operationId`, вы можете перебрать их все и переопределить `operation_id` каждой *операции пути*, используя `APIRoute.name`. |
|||
|
|||
Необходимо сделать это после добавления всех ваших *операций пути*. |
|||
|
|||
{* ../../docs_src/path_operation_advanced_configuration/tutorial002.py hl[2, 12:21, 24] *} |
|||
|
|||
/// tip | Совет |
|||
|
|||
Если вы вручную вызываете `app.openapi()`, обновите `operationId` до этого. |
|||
|
|||
/// |
|||
|
|||
/// warning | Предупреждение |
|||
|
|||
Если вы это делаете, необходимо удостовериться, что каждая из ваших *функций операций пути* имеет уникальное имя. |
|||
|
|||
Даже если они находятся в разных модулях (файлах Python). |
|||
|
|||
/// |
|||
|
|||
## Исключение из OpenAPI |
|||
|
|||
Чтобы исключить *операцию пути* из генерируемой схемы OpenAPI (и таким образом, из систем автоматической документации), используйте параметр `include_in_schema` и установите его значение как `False`: |
|||
|
|||
{* ../../docs_src/path_operation_advanced_configuration/tutorial003.py hl[6] *} |
|||
|
|||
## Расширенное описание из строки документации |
|||
|
|||
Вы можете ограничить количество строк, используемых из строки документации функции *операции пути* для OpenAPI. |
|||
|
|||
Добавление `\f` (экранированный символ "перевод страницы") приведет к тому, что **FastAPI** обрежет вывод, используемый для OpenAPI, на этом месте. |
|||
|
|||
Этот символ не покажется в документации, но другие инструменты (такие как Sphinx) смогут использовать остальную часть. |
|||
|
|||
{* ../../docs_src/path_operation_advanced_configuration/tutorial004.py hl[19:29] *} |
|||
|
|||
## Дополнительные ответы |
|||
|
|||
Вы, вероятно, уже знаете, как объявить `response_model` и `status_code` для *операции пути*. |
|||
|
|||
Это определяет метаданные об основном ответе *операции пути*. |
|||
|
|||
Вы также можете объявить дополнительные ответы с их моделями, статус-кодами и т.д. |
|||
|
|||
В документации есть целая глава об этом, которую вы можете прочитать в разделе [Дополнительные ответы в OpenAPI](additional-responses.md){.internal-link target=_blank}. |
|||
|
|||
## Дополнения OpenAPI |
|||
|
|||
Когда вы объявляете *операцию пути* в вашем приложении, **FastAPI** автоматически генерирует соответствующие метаданные об этой *операции пути*, чтобы включить их в схему OpenAPI. |
|||
|
|||
/// note | Технические детали |
|||
|
|||
В спецификации OpenAPI это называется <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#operation-object" class="external-link" target="_blank">Объект операции</a>. |
|||
|
|||
/// |
|||
|
|||
Это содержит всю информацию о *операции пути* и используется для генерации автоматической документации. |
|||
|
|||
Это включает в себя `tags`, `parameters`, `requestBody`, `responses` и т.д. |
|||
|
|||
Эта схема OpenAPI для данной *операции пути* обычно генерируется автоматически **FastAPI**, но ее можно и расширить. |
|||
|
|||
/// tip | Совет |
|||
|
|||
Это точка низкоуровневого расширения. |
|||
|
|||
Если вам нужно только объявить дополнительные ответы, более удобный способ сделать это — через [Дополнительные ответы в OpenAPI](additional-responses.md){.internal-link target=_blank}. |
|||
|
|||
/// |
|||
|
|||
Вы можете расширить схему OpenAPI для *операции пути* с помощью параметра `openapi_extra`. |
|||
|
|||
### Расширения OpenAPI |
|||
|
|||
Этот `openapi_extra` может быть полезен, например, чтобы объявить [Расширения OpenAPI](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions): |
|||
|
|||
{* ../../docs_src/path_operation_advanced_configuration/tutorial005.py hl[6] *} |
|||
|
|||
Если вы откроете автоматическую документацию API, ваше расширение появится внизу конкретной *операции пути*. |
|||
|
|||
<img src="/img/tutorial/path-operation-advanced-configuration/image01.png"> |
|||
|
|||
И если вы увидите результат OpenAPI (по адресу `/openapi.json` в вашем API), вы увидите ваше расширение также как часть конкретной *операции пути*: |
|||
|
|||
```JSON hl_lines="22" |
|||
{ |
|||
"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": {} |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"x-aperture-labs-portal": "blue" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
### Пользовательская схема OpenAPI *операции пути* |
|||
|
|||
Словарь в `openapi_extra` будет глубоко объединен с автоматически сгенерированной схемой OpenAPI для *операции пути*. |
|||
|
|||
Так вы можете добавить дополнительные данные в автоматически сгенерированную схему. |
|||
|
|||
Например, вы можете решить сами считывать и валидировать запрос без использования автоматических функций FastAPI с Pydantic, но вы все равно можете захотеть определить запрос в схеме OpenAPI. |
|||
|
|||
Вы можете сделать это с помощью `openapi_extra`: |
|||
|
|||
{* ../../docs_src/path_operation_advanced_configuration/tutorial006.py hl[19:36, 39:40] *} |
|||
|
|||
В этом примере мы не объявили никакой Pydantic-модели. На самом деле тело запроса даже не <abbr title="преобразовано из какого-либо простого формата, как байты, в Python-объекты">проанализировано</abbr> как JSON, оно считывается напрямую как `bytes`, и функция `magic_data_reader()` будет отвечать за его парсинг. |
|||
|
|||
Тем не менее, мы можем объявить ожидаемую схему для тела запроса. |
|||
|
|||
### Пользовательский тип содержимого OpenAPI |
|||
|
|||
Используя этот же трюк, вы можете использовать Pydantic-модель для определения JSON-схемы, которая затем будет включена в пользовательский раздел схемы OpenAPI для *операции пути*. |
|||
|
|||
И вы можете сделать это даже если тип данных в запросе не JSON. |
|||
|
|||
Например, в этом приложении мы не используем интегрированные функции FastAPI для извлечения JSON-схемы из Pydantic-моделей и автоматической валидации для JSON. В самом деле, мы объявляем тип содержимого запроса как YAML, а не JSON: |
|||
|
|||
//// tab | Pydantic v2 |
|||
|
|||
{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[17:22, 24] *} |
|||
|
|||
//// |
|||
|
|||
//// tab | Pydantic v1 |
|||
|
|||
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py hl[17:22, 24] *} |
|||
|
|||
//// |
|||
|
|||
/// info | Информация |
|||
|
|||
В версии Pydantic 1 метод для получения JSON-схемы для модели назывался `Item.schema()`, в версии Pydantic 2 метод называется `Item.model_json_schema()`. |
|||
|
|||
/// |
|||
|
|||
Тем не менее, хотя мы не используем стандартную интегрированную функциональность, мы все же используем модель Pydantic, чтобы вручную сгенерировать JSON-схему для данных, которые мы хотим получить в YAML. |
|||
|
|||
Затем мы используем запрос напрямую и извлекаем тело как `bytes`. Это означает, что FastAPI даже не будет пытаться парсить содержимое запроса как JSON. |
|||
|
|||
И затем в нашем коде мы парсим это YAML-содержимое напрямую, и затем снова используем ту же модель Pydantic для валидации содержимого YAML: |
|||
|
|||
//// tab | Pydantic v2 |
|||
|
|||
{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[26:33] *} |
|||
|
|||
//// |
|||
|
|||
//// tab | Pydantic v1 |
|||
|
|||
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py hl[26:33] *} |
|||
|
|||
//// |
|||
|
|||
/// info | Информация |
|||
|
|||
В версии Pydantic 1 метод для парсинга и валидации объекта назывался `Item.parse_obj()`, в версии Pydantic 2 метод называется `Item.model_validate()`. |
|||
|
|||
/// |
|||
|
|||
/// tip | Совет |
|||
|
|||
Здесь мы повторно используем ту же модель Pydantic. |
|||
|
|||
Но таким же образом можно было бы валидировать ее каким-то другим способом. |
|||
|
|||
/// |
@ -0,0 +1,41 @@ |
|||
# HTTP-заголовки ответа |
|||
|
|||
## Использование параметра `Response` |
|||
|
|||
Вы можете объявить параметр типа `Response` в вашей *функции-обработчике пути* (так же, как вы делаете для cookie). |
|||
|
|||
Затем вы можете установить HTTP-заголовки в этом *временном* объекте ответа. |
|||
|
|||
{* ../../docs_src/response_headers/tutorial002.py hl[1, 7:8] *} |
|||
|
|||
После этого вы можете вернуть любой объект, который вам нужен, как обычно (например, `dict`, модель базы данных и т.д.). |
|||
|
|||
Если вы объявили `response_model`, он все равно будет использован для фильтрации и преобразования объекта, который вы вернули. |
|||
|
|||
**FastAPI** использует этот *временный* ответ для извлечения HTTP-заголовков (также cookie и статус-код ответа) и помещает их в окончательный ответ, который содержит значение, которое вы вернули и которое фильтруется любым `response_model`. |
|||
|
|||
Вы также можете объявить параметр `Response` в зависимостях и установить HTTP-заголовки (и cookie) в них. |
|||
|
|||
## Непосредственный возврат `Response` |
|||
|
|||
Вы также можете добавить HTTP-заголовки при непосредственном возврате объекта типа `Response`. |
|||
|
|||
Создайте ответ, как описано в разделе [Непосредственный возврат ответа](response-directly.md){.internal-link target=_blank}, и передайте HTTP-заголовки в качестве дополнительного параметра: |
|||
|
|||
{* ../../docs_src/response_headers/tutorial001.py hl[10:12] *} |
|||
|
|||
/// note | Технические детали |
|||
|
|||
Вы также могли бы использовать `from starlette.responses import Response` или `from starlette.responses import JSONResponse`. |
|||
|
|||
**FastAPI** предоставляет те же `starlette.responses`, что и `fastapi.responses`, для удобства разработчика. Но большинство доступных ответов поступает непосредственно из Starlette. |
|||
|
|||
И так как `Response` может часто использоваться для установки HTTP-заголовков и cookie, **FastAPI** также предоставляет его в `fastapi.Response`. |
|||
|
|||
/// |
|||
|
|||
## Пользовательские заголовки |
|||
|
|||
Имейте в виду, что можно добавлять проприетарные пользовательские HTTP-заголовки <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)](../tutorial/cors.md){.internal-link target=_blank}), используя параметр `expose_headers`, документированный в <a href="https://www.starlette.io/middleware/#corsmiddleware" class="external-link" target="_blank">документации Starlette по CORS</a>. |
@ -0,0 +1,107 @@ |
|||
# HTTP Basic Auth |
|||
|
|||
Для самых простых случаев можно использовать HTTP Basic Auth. |
|||
|
|||
В HTTP Basic Auth приложение ожидает HTTP-заголовок, содержащий имя пользователя и пароль. |
|||
|
|||
Если он не получит их, то вернет ошибку HTTP 401 "Unauthorized" (неавторизовано). |
|||
|
|||
Также будет возвращен HTTP-заголовок `WWW-Authenticate` со значением `Basic` и опциональным параметром `realm`. |
|||
|
|||
Это говорит браузеру показать встроенную панель для ввода имени пользователя и пароля. |
|||
|
|||
Затем, когда вы вводите эти имя пользователя и пароль, браузер отправляет их в заголовке автоматически. |
|||
|
|||
## Простая HTTP Basic Auth |
|||
|
|||
* Импортируйте `HTTPBasic` и `HTTPBasicCredentials`. |
|||
* Создайте "`security` scheme" используя `HTTPBasic`. |
|||
* Используйте эту `security` зависимость в вашей *операции пути*. |
|||
* Она возвращает объект типа `HTTPBasicCredentials`: |
|||
* Содержит отправленные `username` и `password`. |
|||
|
|||
{* ../../docs_src/security/tutorial006_an_py39.py hl[4,8,12] *} |
|||
|
|||
Когда вы попытаетесь открыть URL в первый раз (или нажмете кнопку "Execute" в документации) браузер спросит вас имя пользователя и пароль: |
|||
|
|||
<img src="/img/tutorial/security/image12.png"> |
|||
|
|||
## Проверка имени пользователя |
|||
|
|||
Вот более полный пример. |
|||
|
|||
Используйте зависимость, чтобы проверить, правильные ли имя пользователя и пароль. |
|||
|
|||
Для этого используйте стандартный модуль Python <a href="https://docs.python.org/3/library/secrets.html" class="external-link" target="_blank">`secrets`</a> для проверки имени пользователя и пароля. |
|||
|
|||
`secrets.compare_digest()` требует `bytes` или `str`, содержащий только ASCII-символы (те, что на английском), это значит, что он не будет работать с символами, такими как `á`, например в `Sebastián`. |
|||
|
|||
Чтобы с этим справиться, мы сначала конвертируем `username` и `password` в `bytes`, кодируя их в UTF-8. |
|||
|
|||
Затем мы можем использовать `secrets.compare_digest()` для гарантии, что `credentials.username` равен `"stanleyjobson"`, а `credentials.password` равен `"swordfish"`. |
|||
|
|||
{* ../../docs_src/security/tutorial007_an_py39.py hl[1,12:24] *} |
|||
|
|||
Это будет аналогично: |
|||
|
|||
```Python |
|||
if not (credentials.username == "stanleyjobson") or not (credentials.password == "swordfish"): |
|||
# Return some error |
|||
... |
|||
``` |
|||
|
|||
Но, используя `secrets.compare_digest()`, это будет защищено от атак под названием "тайминговые атаки". |
|||
|
|||
### Тайминговые атаки |
|||
|
|||
Что такое "тайминговая атака"? |
|||
|
|||
Представим, что некоторая группа злоумышленников пытается угадать имя пользователя и пароль. |
|||
|
|||
Они отправляют запрос с именем пользователя `johndoe` и паролем `love123`. |
|||
|
|||
Тогда Python-код в вашем приложении будет эквивалентен чему-то вроде: |
|||
|
|||
```Python |
|||
if "johndoe" == "stanleyjobson" and "love123" == "swordfish": |
|||
... |
|||
``` |
|||
|
|||
Но в тот момент, когда Python сравнивает первую `j` в `johndoe` с первой `s` в `stanleyjobson`, он вернет `False`, так как уже знает, что эти две строки не одинаковы, считая, что "нет смысла тратить больше вычислительных ресурсов на сравнение остальных букв". И ваше приложение скажет "Неправильное имя пользователя или пароль". |
|||
|
|||
Но затем злоумышленники попробуют с именем пользователя `stanleyjobsox` и паролем `love123`. |
|||
|
|||
И ваш код приложения делает что-то вроде: |
|||
|
|||
```Python |
|||
if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish": |
|||
... |
|||
``` |
|||
|
|||
Python должен будет сравнить весь `stanleyjobso` в `stanleyjobsox` и `stanleyjobson`, чтобы понять, что строки не одинаковы. Так что это займет дополнительные микросекунды, чтобы ответить "Неправильное имя пользователя или пароль". |
|||
|
|||
#### Время ответа помогает злоумышленникам |
|||
|
|||
В этот момент, заметив, что серверу понадобилось немного больше времени, чтобы отправить ответ "Неправильное имя пользователя или пароль", злоумышленники узнают, что они что-то угадали, некоторые начальные буквы были правильными. |
|||
|
|||
И тогда они могут попробовать снова, зная, что это, вероятно, что-то более похожее на `stanleyjobsox`, чем на `johndoe`. |
|||
|
|||
#### "Профессиональная" атака |
|||
|
|||
Конечно, злоумышленники не будут делать все это вручную, они напишут программу, чтобы делать это, возможно, с тысячами или миллионами тестов в секунду. И они будут получать лишь одну правильную букву за раз. |
|||
|
|||
Но, делая так, за несколько минут или часов злоумышленники могли бы угадать правильное имя пользователя и пароль, с "помощью" нашего приложения, просто используя время, затраченное на ответ. |
|||
|
|||
#### Исправьте это с помощью `secrets.compare_digest()` |
|||
|
|||
Но в нашем коде мы фактически используем `secrets.compare_digest()`. |
|||
|
|||
Вкратце, это займет одинаковое время, чтобы сравнить `stanleyjobsox` с `stanleyjobson`, как и `johndoe` с `stanleyjobson`. И то же самое для пароля. |
|||
|
|||
Таким образом, используя `secrets.compare_digest()` в коде вашего приложения, оно будет защищено от этого целого спектра атак на безопасность. |
|||
|
|||
### Возврат ошибки |
|||
|
|||
После обнаружения, что учетные данные неверны, верните `HTTPException` с кодом состояния 401 (тот же, что возвращается, когда учетные данные не предоставлены) и добавьте заголовок `WWW-Authenticate`, чтобы браузер снова отобразил приглашение для входа: |
|||
|
|||
{* ../../docs_src/security/tutorial007_an_py39.py hl[26:30] *} |
@ -0,0 +1,19 @@ |
|||
# Расширенная безопасность |
|||
|
|||
## Дополнительные функции |
|||
|
|||
Существуют дополнительные функции для обеспечения безопасности, кроме тех, которые рассмотрены в [Руководстве пользователя: Безопасность](../../tutorial/security/index.md){.internal-link target=_blank}. |
|||
|
|||
/// tip | Совет |
|||
|
|||
Следующие разделы **не обязательно "продвинутые"**. |
|||
|
|||
И возможно, что для вашего случая решения находятся в одном из них. |
|||
|
|||
/// |
|||
|
|||
## Сначала прочтите Руководство |
|||
|
|||
Следующие разделы предполагают, что вы уже прочитали основное [Руководство пользователя: Безопасность](../../tutorial/security/index.md){.internal-link target=_blank}. |
|||
|
|||
Все они основаны на тех же концепциях, но позволяют использовать некоторые дополнительные функции. |
@ -0,0 +1,274 @@ |
|||
# OAuth2 scope |
|||
|
|||
Вы можете использовать OAuth2 scope напрямую с **FastAPI**, они интегрированы для работы без проблем. |
|||
|
|||
Это позволит вам иметь более тонкую систему разрешений, следуя стандарту OAuth2, интегрированную в ваше OpenAPI приложение (и документы API). |
|||
|
|||
OAuth2 с scope — это механизм, используемый многими крупными провайдерами аутентификации, такими как Facebook, Google, GitHub, Microsoft, Twitter и т.д. Они используют его для предоставления определенных разрешений пользователям и приложениям. |
|||
|
|||
Каждый раз, когда вы "входите с помощью" Facebook, Google, GitHub, Microsoft, Twitter, это приложение использует OAuth2 с scope. |
|||
|
|||
В этом разделе вы увидите, как управлять аутентификацией и авторизацией с использованием того же OAuth2 с scope в вашем приложении **FastAPI**. |
|||
|
|||
/// warning | Предупреждение |
|||
|
|||
Это более или менее сложный раздел. Если вы только начинаете, вы можете пропустить его. |
|||
|
|||
Вы не обязательно нуждаетесь в OAuth2 scope, и можете обрабатывать аутентификацию и авторизацию, как вам угодно. |
|||
|
|||
Но OAuth2 с scope может быть прекрасно интегрирован в ваш API (с OpenAPI) и в ваши документы API. |
|||
|
|||
Тем не менее, вы все равно должны применять эти scope, или любые другие требования к безопасности/авторизации, так как вам нужно, в вашем коде. |
|||
|
|||
В многих случаях, OAuth2 с scope может быть избыточными. |
|||
|
|||
Но если вы знаете, что они вам нужны, или вы заинтересовались, продолжайте чтение. |
|||
|
|||
/// |
|||
|
|||
## OAuth2 scope и OpenAPI |
|||
|
|||
Спецификация OAuth2 определяет "scope" как список строк, разделенных пробелами. |
|||
|
|||
Содержимое каждой из этих строк может иметь любой формат, но не должно содержать пробелов. |
|||
|
|||
Эти scope представляют собой "разрешения". |
|||
|
|||
В OpenAPI (например, в документах API) вы можете определить "схемы безопасности". |
|||
|
|||
Когда одна из этих схем безопасности использует OAuth2, вы также можете объявлять и использовать scope. |
|||
|
|||
Каждый "scope" — это просто строка (без пробелов). |
|||
|
|||
Обычно они используются для объявления определенных разрешений безопасности, например: |
|||
|
|||
* `users:read` или `users:write` — это распространенные примеры. |
|||
* `instagram_basic` используется Facebook/Instagram. |
|||
* `https://www.googleapis.com/auth/drive` используется Google. |
|||
|
|||
/// info | Информация |
|||
|
|||
В OAuth2 "scope" — это просто строка, указывающая на конкретные требуемые разрешения. |
|||
|
|||
Не имеет значения, есть ли в нем другие символы, такие как `:`, или если это URL. |
|||
|
|||
Эти детали специфичны для реализации. |
|||
|
|||
Для OAuth2 они просто строки. |
|||
|
|||
/// |
|||
|
|||
## Общее представление |
|||
|
|||
Сначала давайте быстро посмотрим на части, которые изменяются по сравнению с примерами в основном **Учебнике - Руководстве пользователя** для [OAuth2 с Паролем (и хешированием), Bearer с JWT токенами](../../tutorial/security/oauth2-jwt.md){.internal-link target=_blank}. Теперь используя OAuth2 scope: |
|||
|
|||
{* ../../docs_src/security/tutorial005_an_py310.py hl[5,9,13,47,65,106,108:116,122:125,129:135,140,156] *} |
|||
|
|||
Теперь давайте рассмотрим эти изменения шаг за шагом. |
|||
|
|||
## Схема безопасности OAuth2 |
|||
|
|||
Первое изменение состоит в том, что мы теперь объявляем схему безопасности OAuth2 с двумя доступными scope, `me` и `items`. |
|||
|
|||
Параметр `scopes` принимает `dict`, где каждый scope указывается в качестве ключа, а описание — в качестве значения: |
|||
|
|||
{* ../../docs_src/security/tutorial005_an_py310.py hl[63:66] *} |
|||
|
|||
Поскольку мы теперь объявляем эти scope, они будут отображаться в документации API, когда вы входите/авторизируетесь. |
|||
|
|||
И вы сможете выбрать, к каким scope вы хотите дать доступ: `me` и `items`. |
|||
|
|||
Это тот же механизм, который используется, когда вы предоставляете разрешения при входе в Facebook, Google, GitHub и т.д: |
|||
|
|||
<img src="/img/tutorial/security/image11.png"> |
|||
|
|||
## JWT токен с scope |
|||
|
|||
Теперь модифицируйте токен *операции пути*, чтобы вернуть запрашиваемые scope. |
|||
|
|||
Мы по-прежнему используем тот же `OAuth2PasswordRequestForm`. Он включает свойство `scopes` с `list` из `str`, в котором содержатся все scope, полученные в запросе. |
|||
|
|||
И мы возвращаем scope как часть JWT токена. |
|||
|
|||
/// danger | Опасность |
|||
|
|||
Для упрощения, здесь мы просто добавляем полученные scope непосредственно в токен. |
|||
|
|||
Но в вашем приложении, с целью безопасности, вы должны удостовериться, что добавляете только те scope, которые пользователь действительно может иметь, или те, которые вы заранее определили. |
|||
|
|||
/// |
|||
|
|||
{* ../../docs_src/security/tutorial005_an_py310.py hl[156] *} |
|||
|
|||
## Объявление scope в *операциях пути* и зависимостях |
|||
|
|||
Теперь мы объявляем, что *операция пути* для `/users/me/items/` требует scope `items`. |
|||
|
|||
Для этого мы импортируем и используем `Security` из `fastapi`. |
|||
|
|||
Вы можете использовать `Security` для объявления зависимостей (так же как `Depends`), но `Security` также принимает параметр `scopes` со списком scope (строк). |
|||
|
|||
В данном случае, мы передаем функцию зависимости `get_current_active_user` в `Security` (так же как мы бы делали это с `Depends`). |
|||
|
|||
Но мы также передаем `list` с scope, в этом случае только с одним scope: `items` (хотя их может быть больше). |
|||
|
|||
И функция зависимости `get_current_active_user` также может объявлять подзависимости, не только с `Depends`, но и с `Security`. Объявляя свою подзависимость (`get_current_user`), а также требования к scope. |
|||
|
|||
В этом случае требуется scope `me` (могут требоваться и другие scope). |
|||
|
|||
/// note | Примечание |
|||
|
|||
Вам не обязательно нужно добавлять разные scope в разные места. |
|||
|
|||
Мы делаем это здесь, чтобы продемонстрировать, как **FastAPI** обрабатывает scope, объявленные на разных уровнях. |
|||
|
|||
/// |
|||
|
|||
{* ../../docs_src/security/tutorial005_an_py310.py hl[5,140,171] *} |
|||
|
|||
/// info | Технические детали |
|||
|
|||
`Security` на самом деле является подклассом `Depends`, и у него есть всего один дополнительный параметр, который мы рассмотрим позже. |
|||
|
|||
Но используя `Security` вместо `Depends`, **FastAPI** будет знать, что он может объявить scope безопасности, использовать их внутри и документировать API с помощью OpenAPI. |
|||
|
|||
Но когда вы импортируете `Query`, `Path`, `Depends`, `Security` и другие из `fastapi`, это на самом деле функции, которые возвращают специальные классы. |
|||
|
|||
/// |
|||
|
|||
## Используйте `SecurityScopes` |
|||
|
|||
Теперь обновите зависимость `get_current_user`. |
|||
|
|||
Это та зависимость, которая используется выше. |
|||
|
|||
Вот где мы используем ту же схему OAuth2, которую создали ранее, объявляя ее как зависимость: `oauth2_scheme`. |
|||
|
|||
Поскольку эта функция зависимости не имеет собственных требований к scope, мы можем использовать `Depends` с `oauth2_scheme`, нам не нужно использовать `Security`, когда нам не нужно указывать scope безопасности. |
|||
|
|||
Также мы объявляем специальный параметр типа `SecurityScopes`, импортированный из `fastapi.security`. |
|||
|
|||
Этот класс `SecurityScopes` похож на `Request` (`Request` использовался для получения объекта запроса напрямую). |
|||
|
|||
{* ../../docs_src/security/tutorial005_an_py310.py hl[9,106] *} |
|||
|
|||
## Используйте `scopes` |
|||
|
|||
Параметр `security_scopes` будет типа `SecurityScopes`. |
|||
|
|||
У него есть свойство `scopes` со списком, содержащим все scope, требуемые как сама зависимость, так и все зависимости, которые используют это как подзависимость. Это значит, все "зависимые"... это может звучать запутанно, но это объясняется снова позже. |
|||
|
|||
Объект `security_scopes` (класса `SecurityScopes`) также предоставляет атрибут `scope_str` с одной строкой, содержащей эти scope, разделенные пробелами (мы собираемся использовать его). |
|||
|
|||
Мы создаем `HTTPException`, который мы можем использовать (`raise`) позже в нескольких повторяющихся моментах. |
|||
|
|||
В этом исключении мы включаем требуемые scope (если они есть) в виде строки, разделенной пробелами (используя `scope_str`). Мы помещаем эту строку, содержащую scope, в HTTP-заголовке `WWW-Authenticate` (это часть спецификации). |
|||
|
|||
{* ../../docs_src/security/tutorial005_an_py310.py hl[106,108:116] *} |
|||
|
|||
## Проверьте `username` и форму данных |
|||
|
|||
Мы проверяем, что получили `username`, и извлекаем scope. |
|||
|
|||
Затем мы валидируем эти данные с помощью Pydantic-модели (обрабатывая исключение `ValidationError`), и если мы получаем ошибку при чтении JWT токена или валидации данных с Pydantic, мы вызываем `HTTPException`, который создали ранее. |
|||
|
|||
Для этого мы обновляем Pydantic-модель `TokenData` с новым свойством `scopes`. |
|||
|
|||
Валидируя данные с помощью Pydantic, мы можем убедиться, что у нас, например, точно есть `list` из `str` с scope и `str` с `username`. |
|||
|
|||
Вместо, например, `dict`, или чего-то еще, так как это может сломать приложение в какой-то момент позже, создавая угрозу безопасности. |
|||
|
|||
Мы также проверяем, что у нас есть пользователь с этим именем пользователя, и если нет, вызываем то же самое исключение, созданное ранее. |
|||
|
|||
{* ../../docs_src/security/tutorial005_an_py310.py hl[47,117:128] *} |
|||
|
|||
## Проверьте `scopes` |
|||
|
|||
Теперь мы проверяем, все ли требуемые scope, этой зависимостью и всеми зависимыми (включая *операции пути*), включены в предоставленные токеном scope, в противном случае вызываем `HTTPException`. |
|||
|
|||
Для этого мы используем `security_scopes.scopes`, который содержит `list` со всеми этими scope как `str`. |
|||
|
|||
{* ../../docs_src/security/tutorial005_an_py310.py hl[129:135] *} |
|||
|
|||
## Дерево зависимостей и scope |
|||
|
|||
Давайте еще раз рассмотрим это дерево зависимостей и scope. |
|||
|
|||
Поскольку зависимость `get_current_active_user` имеет подзависимость на `get_current_user`, scope `"me"`, объявленный в `get_current_active_user`, будет включен в список требуемых scope в `security_scopes.scopes`, переданном в `get_current_user`. |
|||
|
|||
Сама *операция пути* также объявляет scope, `"items"`, так что он также будет в списке `security_scopes.scopes`, передаваемом в `get_current_user`. |
|||
|
|||
Вот как выглядит иерархия зависимостей и scope: |
|||
|
|||
* У *операции пути* `read_own_items`: |
|||
* Требуемые scope `["items"]` с зависимостью: |
|||
* `get_current_active_user`: |
|||
* Функция зависимости `get_current_active_user` имеет: |
|||
* Требуемые scope `["me"]` с зависимостью: |
|||
* `get_current_user`: |
|||
* У функции зависимости `get_current_user`: |
|||
* Нет требуемых scope. |
|||
* Зависимость, использующая `oauth2_scheme`. |
|||
* Параметр `security_scopes` типа `SecurityScopes`: |
|||
* У этого параметра `security_scopes` есть свойство `scopes` со `list`, содержащим все scope, объявленные выше, так что: |
|||
* `security_scopes.scopes` будет содержать `["me", "items"]` для *операции пути* `read_own_items`. |
|||
* `security_scopes.scopes` будет содержать `["me"]` для *операции пути* `read_users_me`, потому что он объявлен в зависимости `get_current_active_user`. |
|||
* `security_scopes.scopes` будет содержать `[]` (ничего) для *операции пути* `read_system_status`, потому что там не объявлено никакое `Security` с `scopes`, и его зависимость `get_current_user` также не объявляет никакие `scopes`. |
|||
|
|||
/// tip | Совет |
|||
|
|||
Самое важное и "магическое" здесь то, что `get_current_user` будет иметь разный список `scopes` для проверки для каждой *операции пути*. |
|||
|
|||
Все зависит от `scopes`, объявленных в каждой *операции пути* и каждой зависимости в дереве зависимостей для данной конкретной *операции пути*. |
|||
|
|||
/// |
|||
|
|||
## Подробности о `SecurityScopes` |
|||
|
|||
Вы можете использовать `SecurityScopes` в любой точке и в нескольких местах, он не должен быть на уровне "корневой" зависимости. |
|||
|
|||
Он будет всегда иметь scope безопасности, объявленные в текущих зависимостях `Security` и всех зависимых для **конкретно этой** *операции пути* и **конкретно этого** дерева зависимостей. |
|||
|
|||
Поскольку `SecurityScopes` будут иметь все scope, заявленные зависимыми, вы можете использовать его для проверки того, что токен имеет требуемые scope в центральной функции зависимости, а затем объявлять разные требования к scope в разных *операциях пути*. |
|||
|
|||
Они будут проверяться независимо для каждой *операции пути*. |
|||
|
|||
## Проверьте это |
|||
|
|||
Если вы откроете документацию API, вы можете аутентифицироваться и указать, какие scope вы хотите авторизовать. |
|||
|
|||
<img src="/img/tutorial/security/image11.png"> |
|||
|
|||
Если вы не выберете ни один scope, вы будете "аутентифицированы", но при попытке доступа к `/users/me/` или `/users/me/items/` вы получите ошибку, сообщающую, что у вас недостаточно разрешений. Вы все равно сможете получить доступ к `/status/`. |
|||
|
|||
И если вы выберете scope `me`, но не scope `items`, вы сможете получить доступ к `/users/me/`, но не к `/users/me/items/`. |
|||
|
|||
Это именно то, что произойдет с сторонним приложением, которое пытается получить доступ к одной из этих *операций пути* с токеном, предоставленным пользователем, в зависимости от того, сколько разрешений пользователь дал приложению. |
|||
|
|||
## О сторонних интеграциях |
|||
|
|||
В этом примере мы используем OAuth2 "парольный" поток. |
|||
|
|||
Это уместно, когда мы входим в наше собственное приложение, вероятно, с нашим собственным фронтендом. |
|||
|
|||
Потому что мы можем доверять ему получение `username` и `password`, так как мы его контролируем. |
|||
|
|||
Но если вы строите приложение OAuth2, к которому будут подключаться другие (т.е. если вы создаете провайдер аутентификации, эквивалентный Facebook, Google, GitHub и т.д.), вы должны использовать один из других потоков. |
|||
|
|||
Наиболее распространенным является поток implicit. |
|||
|
|||
Наиболее безопасным является поток с кодом, но его сложнее реализовать, так как он требует больше шагов. Поскольку он более сложен, многие провайдеры в конечном итоге предлагают поток implicit. |
|||
|
|||
/// note | Примечание |
|||
|
|||
Часто каждый провайдер аутентификации называет свои потоки по-разному, чтобы сделать это частью своего бренда. |
|||
|
|||
Но в конечном итоге они реализуют тот же стандарт OAuth2. |
|||
|
|||
/// |
|||
|
|||
**FastAPI** включает утилиты для всех этих потоков аутентификации OAuth2 в `fastapi.security.oauth2`. |
|||
|
|||
## `Security` в `dependencies` декоратора |
|||
|
|||
Таким же образом, как вы можете определять `list` из `Depends` в параметре `dependencies` декоратора (как объяснено в [Dependencies в декораторах операций пути](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), вы также можете использовать `Security` с `scopes` там. |
@ -0,0 +1,346 @@ |
|||
# Настройки и переменные окружения |
|||
|
|||
Во многих случаях вашим приложениям могут понадобиться внешние настройки или конфигурации, например, секретные ключи, учетные данные базы данных, учетные данные для почтовых сервисов и т.д. |
|||
|
|||
Большинство этих настроек являются изменяемыми (могут изменяться), как, например, URL-ы баз данных. Многие из них могут быть чувствительными, как, например, секреты. |
|||
|
|||
По этой причине их обычно предоставляют в виде переменных окружения, которые считываются приложением. |
|||
|
|||
/// tip | Совет |
|||
|
|||
Чтобы понять, что такое переменные окружения, можете прочитать [Переменные окружения](../environment-variables.md){.internal-link target=_blank}. |
|||
|
|||
/// |
|||
|
|||
## Типы и валидация |
|||
|
|||
Эти переменные окружения могут обрабатывать только текстовые строки, так как они являются внешними для Python и должны быть совместимы с другими программами и остальной системой (и даже с разными операционными системами, такими как Linux, Windows, macOS). |
|||
|
|||
Это означает, что любое значение, считанное в Python из переменной окружения, будет типа `str`, и любое преобразование в другой тип или валидация должны осуществляться в коде. |
|||
|
|||
## Pydantic `Settings` |
|||
|
|||
К счастью, Pydantic предоставляет отличную утилиту для работы с этими настройками, поступающими из переменных окружения, используя <a href="https://docs.pydantic.dev/latest/concepts/pydantic_settings/" class="external-link" target="_blank">Pydantic: Управление настройками</a>. |
|||
|
|||
### Установка `pydantic-settings` |
|||
|
|||
Сначала убедитесь, что вы создали ваше [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активировали его и затем установили пакет `pydantic-settings`: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ pip install pydantic-settings |
|||
---> 100% |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
Этот пакет также включен, если вы устанавливаете все зависимости с: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ pip install "fastapi[all]" |
|||
---> 100% |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
/// info | Информация |
|||
|
|||
В Pydantic v1 он поставлялся с основным пакетом. Теперь он распространяется как отдельный пакет, чтобы вы могли выбрать, устанавливать его или нет, если вам не нужна эта функциональность. |
|||
|
|||
/// |
|||
|
|||
### Создание объекта `Settings` |
|||
|
|||
Импортируйте `BaseSettings` из Pydantic и создайте подкласс, как это делается с Pydantic-моделью. |
|||
|
|||
Так же, как и с Pydantic-моделями, вы объявляете атрибуты класса с аннотациями типов и, возможно, значениями по умолчанию. |
|||
|
|||
Вы можете использовать все те же функции валидации и инструменты, которые используете в Pydantic-моделях, например, разные типы данных и дополнительные валидации с `Field()`. |
|||
|
|||
//// tab | Pydantic v2 |
|||
|
|||
{* ../../docs_src/settings/tutorial001.py hl[2,5:8,11] *} |
|||
|
|||
//// |
|||
|
|||
//// tab | Pydantic v1 |
|||
|
|||
/// info | Информация |
|||
|
|||
В Pydantic v1 вы импортировали бы `BaseSettings` непосредственно из `pydantic` вместо `pydantic_settings`. |
|||
|
|||
/// |
|||
|
|||
{* ../../docs_src/settings/tutorial001_pv1.py hl[2,5:8,11] *} |
|||
|
|||
//// |
|||
|
|||
/// tip | Совет |
|||
|
|||
Если вам нужно что-то быстрое для копирования и вставки, не используйте этот пример, используйте последний ниже. |
|||
|
|||
/// |
|||
|
|||
Когда вы создаете экземпляр этого класса `Settings` (в данном случае, в объекте `settings`), Pydantic будет считывать переменные окружения без учета регистра, так что переменная в верхнем регистре `APP_NAME` все еще будет считана для атрибута `app_name`. |
|||
|
|||
Затем он выполнит преобразование и валидацию данных. Таким образом, когда вы используете этот объект `settings`, у вас будут данные тех типов, которые вы объявили (например, `items_per_user` будет типа `int`). |
|||
|
|||
### Использование `settings` |
|||
|
|||
Затем вы можете использовать новый объект `settings` в вашем приложении: |
|||
|
|||
{* ../../docs_src/settings/tutorial001.py hl[18:20] *} |
|||
|
|||
### Запуск сервера |
|||
|
|||
Далее вы можете запустить сервер, передавая конфигурации как переменные окружения, например, вы можете установить `ADMIN_EMAIL` и `APP_NAME` следующим образом: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.py |
|||
|
|||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
/// tip | Совет |
|||
|
|||
Чтобы установить несколько переменных окружения для одной команды, просто разделите их пробелами и укажите перед командой. |
|||
|
|||
/// |
|||
|
|||
Тогда настройка `admin_email` будет установлена в `"deadpool@example.com"`. |
|||
|
|||
Настройка `app_name` будет иметь значение `"ChimichangApp"`. |
|||
|
|||
А `items_per_user` сохранит свое значение по умолчанию — `50`. |
|||
|
|||
## Настройки в другом модуле |
|||
|
|||
Вы можете поместить эти настройки в другой файл-модуль, как вы видели в [Более крупные приложения - Несколько файлов](../tutorial/bigger-applications.md){.internal-link target=_blank}. |
|||
|
|||
Например, у вас может быть файл `config.py` с: |
|||
|
|||
{* ../../docs_src/settings/app01/config.py *} |
|||
|
|||
И затем использовать его в файле `main.py`: |
|||
|
|||
{* ../../docs_src/settings/app01/main.py hl[3,11:13] *} |
|||
|
|||
/// tip | Совет |
|||
|
|||
Вам также нужен будет файл `__init__.py`, как вы видели в [Более крупные приложения - Несколько файлов](../tutorial/bigger-applications.md){.internal-link target=_blank}. |
|||
|
|||
/// |
|||
|
|||
## Настройки в зависимости |
|||
|
|||
В некоторых случаях может быть полезно предоставлять настройки из зависимости, вместо использования глобального объекта `settings`, который используется везде. |
|||
|
|||
Это может быть особенно полезно во время тестирования, так как очень легко переопределить зависимость вашими собственными настройками. |
|||
|
|||
### Файл конфигурации |
|||
|
|||
Исходя из предыдущего примера, ваш файл `config.py` может выглядеть так: |
|||
|
|||
{* ../../docs_src/settings/app02/config.py hl[10] *} |
|||
|
|||
Обратите внимание, что теперь мы не создаем экземпляр по умолчанию `settings = Settings()`. |
|||
|
|||
### Основной файл приложения |
|||
|
|||
Теперь мы создаем зависимость, которая возвращает новый `config.Settings()`. |
|||
|
|||
{* ../../docs_src/settings/app02_an_py39/main.py hl[6,12:13] *} |
|||
|
|||
/// tip | Совет |
|||
|
|||
Мы обсудим `@lru_cache` чуть позже. |
|||
|
|||
На данный момент можно считать, что `get_settings()` — это обычная функция. |
|||
|
|||
/// |
|||
|
|||
А затем мы можем потребовать его в *функции-обработчике пути* как зависимость и использовать ее в любом месте, где это необходимо. |
|||
|
|||
{* ../../docs_src/settings/app02_an_py39/main.py hl[17,19:21] *} |
|||
|
|||
### Настройки и тестирование |
|||
|
|||
Затем вам будет очень легко предоставить другой объект настроек во время тестирования, создав переопределение зависимости для `get_settings`: |
|||
|
|||
{* ../../docs_src/settings/app02/test_main.py hl[9:10,13,21] *} |
|||
|
|||
В переопределении зависимости мы устанавливаем новое значение для `admin_email`, создавая новый объект `Settings`, а затем возвращаем этот новый объект. |
|||
|
|||
Затем мы можем протестировать, что он используется. |
|||
|
|||
## Чтение файла `.env` |
|||
|
|||
Если у вас есть множество настроек, которые могут часто меняться, возможно, в разных окружениях, может быть полезно поместить их в файл и затем считывать их из него, как если бы они были переменными окружения. |
|||
|
|||
Эта практика достаточно распространена, чтобы у нее было название, эти переменные окружения обычно помещаются в файл `.env`, и этот файл называется "dotenv". |
|||
|
|||
/// tip | Совет |
|||
|
|||
Файл, начинающийся с точки (`.`), является скрытым файлом в системах, похожих на Unix, таких как Linux и macOS. |
|||
|
|||
Но файл dotenv не обязательно должен иметь именно такое имя. |
|||
|
|||
/// |
|||
|
|||
Pydantic поддерживает считывание из таких файлов с использованием внешней библиотеки. Вы можете узнать больше на <a href="https://docs.pydantic.dev/latest/concepts/pydantic_settings/#dotenv-env-support" class="external-link" target="_blank">Pydantic Settings: Поддержка Dotenv (.env)</a>. |
|||
|
|||
/// tip | Совет |
|||
|
|||
Для этого необходимо выполнить `pip install python-dotenv`. |
|||
|
|||
/// |
|||
|
|||
### Файл `.env` |
|||
|
|||
Вы могли бы иметь файл `.env` с: |
|||
|
|||
```bash |
|||
ADMIN_EMAIL="deadpool@example.com" |
|||
APP_NAME="ChimichangApp" |
|||
``` |
|||
|
|||
### Чтение настроек из `.env` |
|||
|
|||
А затем обновить ваш `config.py` следующим образом: |
|||
|
|||
//// tab | Pydantic v2 |
|||
|
|||
{* ../../docs_src/settings/app03_an/config.py hl[9] *} |
|||
|
|||
/// tip | Совет |
|||
|
|||
Атрибут `model_config` используется только для настройки Pydantic. Вы можете прочитать больше на <a href="https://docs.pydantic.dev/latest/concepts/config/" class="external-link" target="_blank">Pydantic: Концепции: Конфигурация</a>. |
|||
|
|||
/// |
|||
|
|||
//// |
|||
|
|||
//// tab | Pydantic v1 |
|||
|
|||
{* ../../docs_src/settings/app03_an/config_pv1.py hl[9:10] *} |
|||
|
|||
/// tip | Совет |
|||
|
|||
Класс `Config` используется только для настройки Pydantic. Вы можете прочитать больше на <a href="https://docs.pydantic.dev/1.10/usage/model_config/" class="external-link" target="_blank">Конфигурация Pydantic модели</a>. |
|||
|
|||
/// |
|||
|
|||
//// |
|||
|
|||
/// info | Информация |
|||
|
|||
В версии Pydantic 1 конфигурация осуществлялась во внутреннем классе `Config`, в версии Pydantic 2 это делается в атрибуте `model_config`. Этот атрибут принимает `dict`, и чтобы получить автозавершение и встроенные ошибки, вы можете импортировать и использовать `SettingsConfigDict` для определения этого `dict`. |
|||
|
|||
/// |
|||
|
|||
Здесь мы определяем конфигурацию `env_file` внутри вашего класса Pydantic `Settings` и устанавливаем значение для имени файла с файлом dotenv, который мы хотим использовать. |
|||
|
|||
### Создание `Settings` только один раз с `lru_cache` |
|||
|
|||
Чтение файла с диска обычно является дорогой (медленной) операцией, поэтому, вероятно, вы захотите делать это только один раз, а затем использовать тот же объект настроек, вместо того чтобы считывать его для каждого запроса. |
|||
|
|||
Но каждый раз, когда мы выполняем: |
|||
|
|||
```Python |
|||
Settings() |
|||
``` |
|||
|
|||
новый объект `Settings` будет создан, и при создании он снова считает файл `.env`. |
|||
|
|||
Если бы функция зависимости выглядела так: |
|||
|
|||
```Python |
|||
def get_settings(): |
|||
return Settings() |
|||
``` |
|||
|
|||
мы бы создавали этот объект для каждого запроса, и мы бы считывали файл `.env` для каждого запроса. ⚠️ |
|||
|
|||
Но так как мы используем декоратор `@lru_cache` сверху, объект `Settings` будет создан только один раз, при первом вызове. ✔️ |
|||
|
|||
{* ../../docs_src/settings/app03_an_py39/main.py hl[1,11] *} |
|||
|
|||
Затем для всех последующих вызовов `get_settings()` в зависимостях для следующих запросов, вместо выполнения внутреннего кода функции `get_settings()` и создания нового объекта `Settings`, будет возвращен тот же объект, который был возвращен при первом вызове, снова и снова. |
|||
|
|||
#### Технические детали `lru_cache` |
|||
|
|||
`@lru_cache` модифицирует функцию, которую он декорирует, чтобы возвращать то же самое значение, которое было возвращено в первый раз, вместо того чтобы вычислять его снова, выполняя код функции каждый раз. |
|||
|
|||
Таким образом, функция ниже будет выполнена один раз для каждой комбинации аргументов. А затем возвращаемые значения для каждой из этих комбинаций аргументов будут использоваться снова и снова, когда функция вызывается с точно такой же комбинацией аргументов. |
|||
|
|||
Например, если у вас есть функция: |
|||
|
|||
```Python |
|||
@lru_cache |
|||
def say_hi(name: str, salutation: str = "Ms."): |
|||
return f"Hello {salutation} {name}" |
|||
``` |
|||
|
|||
ваша программа может выполняться так: |
|||
|
|||
```mermaid |
|||
sequenceDiagram |
|||
|
|||
participant code as Код |
|||
participant function as say_hi() |
|||
participant execute as Выполнить код функции |
|||
|
|||
rect rgba(0, 255, 0, .1) |
|||
code ->> function: say_hi(name="Camila") |
|||
function ->> execute: выполнить код функции |
|||
execute ->> code: вернуть результат |
|||
end |
|||
|
|||
rect rgba(0, 255, 255, .1) |
|||
code ->> function: say_hi(name="Camila") |
|||
function ->> code: вернуть сохраненный результат |
|||
end |
|||
|
|||
rect rgba(0, 255, 0, .1) |
|||
code ->> function: say_hi(name="Rick") |
|||
function ->> execute: выполнить код функции |
|||
execute ->> code: вернуть результат |
|||
end |
|||
|
|||
rect rgba(0, 255, 0, .1) |
|||
code ->> function: say_hi(name="Rick", salutation="Mr.") |
|||
function ->> execute: выполнить код функции |
|||
execute ->> code: вернуть результат |
|||
end |
|||
|
|||
rect rgba(0, 255, 255, .1) |
|||
code ->> function: say_hi(name="Rick") |
|||
function ->> code: вернуть сохраненный результат |
|||
end |
|||
|
|||
rect rgba(0, 255, 255, .1) |
|||
code ->> function: say_hi(name="Camila") |
|||
function ->> code: вернуть сохраненный результат |
|||
end |
|||
``` |
|||
|
|||
В случае нашей зависимости `get_settings()`, функция даже не принимает никаких аргументов, поэтому всегда возвращает одно и то же значение. |
|||
|
|||
Таким образом, она ведет себя почти как глобальная переменная. Но поскольку используется функция зависимости, мы можем легко переопределить ее для тестирования. |
|||
|
|||
`@lru_cache` является частью `functools`, который является частью стандартной библиотеки Python, вы можете прочитать больше об этом в <a href="https://docs.python.org/3/library/functools.html#functools.lru_cache" class="external-link" target="_blank">документации Python по `@lru_cache`</a>. |
|||
|
|||
## Резюме |
|||
|
|||
Вы можете использовать Pydantic Settings для управления настройками или конфигурациями вашего приложения, используя все возможности Pydantic-моделей. |
|||
|
|||
* Используя зависимость, вы можете упростить тестирование. |
|||
* Вы можете использовать `.env` файлы с ним. |
|||
* Использование `@lru_cache` позволяет избежать повторного чтения файла dotenv для каждого запроса, при этом позволяя его переопределять во время тестирования. |
@ -0,0 +1,67 @@ |
|||
# Подприложения - Mounts |
|||
|
|||
Если вам нужно иметь два независимых FastAPI приложения, каждое с собственным, независимым OpenAPI и собственными интерфейсами документации, вы можете создать основное приложение и "смонтировать" одно (или более) подприложение(я). |
|||
|
|||
## Монтирование **FastAPI** приложения |
|||
|
|||
"Монтирование" означает добавление полностью "независимого" приложения в определенный path, которое затем будет обрабатывать все под этим path, с _операциями пути_, объявленными в этом подприложении. |
|||
|
|||
### Высокоуровневое приложение |
|||
|
|||
Сначала создайте основное, высшего уровня, **FastAPI** приложение и его *операции пути*: |
|||
|
|||
{* ../../docs_src/sub_applications/tutorial001.py hl[3, 6:8] *} |
|||
|
|||
### Подприложение |
|||
|
|||
Затем создайте ваше подприложение и его *операции пути*. |
|||
|
|||
Это подприложение является стандартным FastAPI приложением, но именно оно будет "смонтировано": |
|||
|
|||
{* ../../docs_src/sub_applications/tutorial001.py hl[11, 14:16] *} |
|||
|
|||
### Монтирование подприложения |
|||
|
|||
В вашем высшего уровня приложении, `app`, смонтируйте подприложение, `subapi`. |
|||
|
|||
В этом случае оно будет смонтировано на path `/subapi`: |
|||
|
|||
{* ../../docs_src/sub_applications/tutorial001.py hl[11, 19] *} |
|||
|
|||
### Проверьте автоматическую документацию API |
|||
|
|||
Теперь запустите команду `fastapi` с вашим файлом: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ fastapi dev main.py |
|||
|
|||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
И откройте документацию по адресу <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/sub-applications/image01.png"> |
|||
|
|||
Затем откройте документацию для подприложения по адресу <a href="http://127.0.0.1:8000/subapi/docs" class="external-link" target="_blank">http://127.0.0.1:8000/subapi/docs</a>. |
|||
|
|||
Вы увидите автоматическую документацию API для подприложения, включающую только его собственные _операции пути_, все с правильным префиксом под-path `/subapi`: |
|||
|
|||
<img src="/img/tutorial/sub-applications/image02.png"> |
|||
|
|||
Если вы попробуете взаимодействовать с любым из двух пользовательских интерфейсов, они будут работать правильно, потому что браузер сможет обратиться к каждому конкретному приложению или подприложению. |
|||
|
|||
### Технические детали: `root_path` |
|||
|
|||
Когда вы монтируете подприложение, как описано выше, FastAPI позаботится о передаче пути монтирования для подприложения, используя механизм из спецификации ASGI, называемый `root_path`. |
|||
|
|||
Таким образом, подприложение будет знать, что необходимо использовать этот префикс пути для UI документации. |
|||
|
|||
И подприложение также может иметь свои собственные смонтированные подприложения, и всё будет работать корректно, потому что FastAPI обрабатывает все эти `root_path`s автоматически. |
|||
|
|||
Вы узнаете больше о `root_path` и том, как его использовать явно в разделе про [Работу за прокси-сервером](behind-a-proxy.md){.internal-link target=_blank}. |
@ -0,0 +1,126 @@ |
|||
# Шаблоны |
|||
|
|||
Вы можете использовать любой шаблонизатор, который захотите, с **FastAPI**. |
|||
|
|||
Популярным выбором является Jinja2, который также используется Flask и другими инструментами. |
|||
|
|||
Существуют утилиты для его простой настройки, которые вы можете использовать непосредственно в своем приложении **FastAPI** (предоставлено Starlette). |
|||
|
|||
## Установка зависимостей |
|||
|
|||
Убедитесь, что вы создали [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активировали его и установили `jinja2`: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ pip install jinja2 |
|||
|
|||
---> 100% |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
## Использование `Jinja2Templates` |
|||
|
|||
* Импортируйте `Jinja2Templates`. |
|||
* Создайте объект `templates`, который вы сможете повторно использовать позднее. |
|||
* Объявите параметр `Request` в *операции пути*, которая будет возвращать шаблон. |
|||
* Используйте созданные вами `templates` для рендеринга и возврата `TemplateResponse`, передайте имя шаблона, объект запроса и словарь "context" с парами "ключ-значение", которые будут использоваться внутри шаблона Jinja2. |
|||
|
|||
{* ../../docs_src/templates/tutorial001.py hl[4,11,15:18] *} |
|||
|
|||
/// note | Примечание |
|||
|
|||
До FastAPI 0.108.0, Starlette 0.29.0, параметр `name` был первым. |
|||
|
|||
Также, до этого, в предыдущих версиях объект `request` передавался как часть пар ключ-значение в контексте для Jinja2. |
|||
|
|||
/// |
|||
|
|||
/// tip | Совет |
|||
|
|||
Объявляя `response_class=HTMLResponse`, интерфейс документации сможет определить, что ответ будет в формате HTML. |
|||
|
|||
/// |
|||
|
|||
/// note | Технические детали |
|||
|
|||
Вы также можете использовать `from starlette.templating import Jinja2Templates`. |
|||
|
|||
**FastAPI** предоставляет тот же `starlette.templating` как `fastapi.templating` просто для вашего удобства, разработчик. Но большинство доступных ответов поступают непосредственно от Starlette. То же самое касается `Request` и `StaticFiles`. |
|||
|
|||
/// |
|||
|
|||
## Написание шаблонов |
|||
|
|||
Затем вы можете написать шаблон в `templates/item.html`, например: |
|||
|
|||
```jinja hl_lines="7" |
|||
{!../../docs_src/templates/templates/item.html!} |
|||
``` |
|||
|
|||
### Значения контекста шаблона |
|||
|
|||
В HTML, который содержит: |
|||
|
|||
{% raw %} |
|||
|
|||
```jinja |
|||
Item ID: {{ id }} |
|||
``` |
|||
|
|||
{% endraw %} |
|||
|
|||
...будет показан `id`, взятый из "context" `dict`, который вы передали: |
|||
|
|||
```Python |
|||
{"id": id} |
|||
``` |
|||
|
|||
Например, с `id`, равным `42`, это будет рендериться как: |
|||
|
|||
```html |
|||
Item ID: 42 |
|||
``` |
|||
|
|||
### Аргументы `url_for` в шаблоне |
|||
|
|||
Вы также можете использовать `url_for()` внутри шаблона; он принимает в качестве аргументов те же аргументы, которые использовались бы в вашей *функции-обработчике пути*. |
|||
|
|||
Таким образом, раздел с: |
|||
|
|||
{% raw %} |
|||
|
|||
```jinja |
|||
<a href="{{ url_for('read_item', id=id) }}"> |
|||
``` |
|||
|
|||
{% endraw %} |
|||
|
|||
...сгенерирует ссылку на тот же URL, который был бы обработан *функцией-обработчиком пути* `read_item(id=id)`. |
|||
|
|||
Например, с `id`, равным `42`, это будет рендериться как: |
|||
|
|||
```html |
|||
<a href="/items/42"> |
|||
``` |
|||
|
|||
## Шаблоны и статические файлы |
|||
|
|||
Вы также можете использовать `url_for()` внутри шаблона и использовать его, например, с `StaticFiles`, которые вы смонтировали с `name="static"`. |
|||
|
|||
```jinja hl_lines="4" |
|||
{!../../docs_src/templates/templates/item.html!} |
|||
``` |
|||
|
|||
В этом примере это будет ссылка на CSS-файл по пути `static/styles.css` с: |
|||
|
|||
```CSS hl_lines="4" |
|||
{!../../docs_src/templates/static/styles.css!} |
|||
``` |
|||
|
|||
И поскольку вы используете `StaticFiles`, этот CSS-файл будет автоматически обслуживаться вашим приложением **FastAPI** по URL `/static/styles.css`. |
|||
|
|||
## Дополнительные сведения |
|||
|
|||
Для получения дополнительной информации, включая тестирование шаблонов, смотрите <a href="https://www.starlette.io/templates/" class="external-link" target="_blank">документацию Starlette по шаблонам</a>. |
@ -0,0 +1,53 @@ |
|||
# Тестирование зависимостей с использованием переопределений |
|||
|
|||
## Переопределение зависимостей в процессе тестирования |
|||
|
|||
Существуют сценарии, когда вам может понадобиться переопределить зависимость во время тестирования. |
|||
|
|||
Вы не хотите, чтобы оригинальная зависимость выполнялась (также как и любые её подзависимости). |
|||
|
|||
Вместо этого, вы хотите предоставить другую зависимость, которая будет использоваться только во время тестов (возможно, только для некоторых конкретных тестов), и будет предоставлять значение, которое можно использовать там, где использовалось значение оригинальной зависимости. |
|||
|
|||
### Примеры использования: внешний сервис |
|||
|
|||
Примером может служить ситуация, когда у вас есть внешний провайдер аутентификации, которого вам необходимо вызывать. |
|||
|
|||
Вы отправляете ему токен, и он возвращает аутентифицированного пользователя. |
|||
|
|||
Этот провайдер может брать плату за каждый запрос, и вызов его может занять больше времени, чем если бы вы использовали фиксированного пользователя-мока для тестов. |
|||
|
|||
Вероятно, вы захотите протестировать внешнего провайдера один раз, но не обязательно вызывать его для каждого теста, который вы запускаете. |
|||
|
|||
В этом случае вы можете переопределить зависимость, которая вызывает этого провайдера, и использовать пользовательскую зависимость, которая возвращает мока-пользователя, только для ваших тестов. |
|||
|
|||
### Использование атрибута `app.dependency_overrides` |
|||
|
|||
В таких случаях ваше приложение **FastAPI** имеет атрибут `app.dependency_overrides`, который является простым `dict`. |
|||
|
|||
Чтобы переопределить зависимость для тестирования, вы указываете оригинальную зависимость (функцию) в качестве ключа, и ваше переопределение зависимости (другую функцию) в качестве значения. |
|||
|
|||
Затем **FastAPI** будет вызывать это переопределение вместо оригинальной зависимости. |
|||
|
|||
{* ../../docs_src/dependency_testing/tutorial001_an_py310.py hl[26:27,30] *} |
|||
|
|||
/// tip | Совет |
|||
|
|||
Вы можете установить переопределение зависимости для зависимости, используемой в любом месте вашего приложения **FastAPI**. |
|||
|
|||
Оригинальная зависимость может использоваться в *функции-обработчике пути*, *декораторе операции пути* (если вы не используете возвращаемое значение), вызове `.include_router()`, и т. д. |
|||
|
|||
FastAPI все равно сможет ее переопределить. |
|||
|
|||
/// |
|||
|
|||
Затем вы можете сбросить ваши переопределения (удалить их), установив `app.dependency_overrides` в пустой `dict`: |
|||
|
|||
```Python |
|||
app.dependency_overrides = {} |
|||
``` |
|||
|
|||
/// tip | Совет |
|||
|
|||
Если вы хотите переопределить зависимость только в некоторых тестах, вы можете установить переопределение в начале теста (внутри тестовой функции) и сбросить его в конце (в конце тестовой функции). |
|||
|
|||
/// |
@ -0,0 +1,5 @@ |
|||
# Тестирование событий: startup - shutdown |
|||
|
|||
Когда вам нужно, чтобы ваши обработчики событий (`startup` и `shutdown`) запускались в ваших тестах, вы можете использовать `TestClient` с оператором `with`: |
|||
|
|||
{* ../../docs_src/app_testing/tutorial003.py hl[9:12,20:24] *} |
@ -0,0 +1,13 @@ |
|||
# Тестирование WebSockets |
|||
|
|||
Вы можете использовать тот же `TestClient` для тестирования WebSockets. |
|||
|
|||
Для этого вы используете `TestClient` в операторе `with`, подключаясь к WebSocket: |
|||
|
|||
{* ../../docs_src/app_testing/tutorial002.py hl[27:31] *} |
|||
|
|||
/// note | Заметка |
|||
|
|||
Для получения дополнительных сведений обратитесь к документации Starlette для <a href="https://www.starlette.io/testclient/#testing-websocket-sessions" class="external-link" target="_blank">тестирования WebSockets</a>. |
|||
|
|||
/// |
@ -0,0 +1,56 @@ |
|||
# Прямое использование объекта Request |
|||
|
|||
До сих пор вы объявляли части HTTP-запроса, которые вам нужны, с указанием их типов. |
|||
|
|||
Получение данных из: |
|||
|
|||
* path как параметров. |
|||
* HTTP-заголовков. |
|||
* cookies. |
|||
* и т.д. |
|||
|
|||
И благодаря этому, **FastAPI** валидирует эти данные, выполняет их преобразование и автоматически генерирует документацию для вашего API. |
|||
|
|||
Но есть ситуации, когда может понадобиться доступ к объекту `Request` напрямую. |
|||
|
|||
## Подробности об объекте `Request` |
|||
|
|||
Так как **FastAPI** на самом деле построен на **Starlette**, с уровнем дополнительных инструментов сверху, вы можете использовать объект <a href="https://www.starlette.io/requests/" class="external-link" target="_blank">`Request`</a> из Starlette напрямую, когда это необходимо. |
|||
|
|||
Это также означает, что если вы получаете данные из объекта `Request` напрямую (например, читаете тело запроса), они не будут валидироваться, преобразовываться или документироваться (с использованием OpenAPI для автоматического пользовательского интерфейса API) в FastAPI. |
|||
|
|||
Хотя любой другой параметр, объявленный обычно (например, тело запроса с Pydantic моделью), все равно будет валидироваться, преобразовываться, аннотироваться и т.д. |
|||
|
|||
Но есть определенные случаи, когда полезно получить объект `Request`. |
|||
|
|||
## Использование объекта `Request` напрямую |
|||
|
|||
Представьте, что вы хотите получить IP-адрес/хост клиента внутри вашей *функции-обработчика пути*. |
|||
|
|||
Для этого вам нужно будет получить доступ к запросу напрямую. |
|||
|
|||
{* ../../docs_src/using_request_directly/tutorial001.py hl[1,7:8] *} |
|||
|
|||
Объявляя параметр *функции-обработчика пути* с типом `Request`, **FastAPI** поймет, что необходимо передать объект `Request` в этот параметр. |
|||
|
|||
/// tip | Подсказка |
|||
|
|||
Обратите внимание, что в этом случае мы объявляем path параметр вместе с параметром запроса. |
|||
|
|||
Таким образом, path параметр будет извлечен, провалидирован, преобразован в указанный тип и аннотирован с помощью OpenAPI. |
|||
|
|||
Точно так же вы можете объявить любой другой параметр как обычно, и дополнительно получить также объект `Request`. |
|||
|
|||
/// |
|||
|
|||
## Документация по `Request` |
|||
|
|||
Вы можете прочитать больше деталей о <a href="https://www.starlette.io/requests/" class="external-link" target="_blank">объекте `Request` на официальном сайте документации Starlette</a>. |
|||
|
|||
/// note | Технические Подробности |
|||
|
|||
Вы также можете использовать `from starlette.requests import Request`. |
|||
|
|||
**FastAPI** предоставляет это напрямую просто для удобства для вас, разработчика. Но эта функция приходит прямо из Starlette. |
|||
|
|||
/// |
@ -0,0 +1,35 @@ |
|||
# Включение WSGI - Flask, Django и другие |
|||
|
|||
Вы можете монтировать WSGI-приложения, как вы видели в разделе [Дополнительные приложения - Монтирования](sub-applications.md){.internal-link target=_blank}, [За прокси](behind-a-proxy.md){.internal-link target=_blank}. |
|||
|
|||
Для этого вы можете использовать `WSGIMiddleware`, чтобы обернуть ваше WSGI-приложение, например, Flask, Django и т.д. |
|||
|
|||
## Использование `WSGIMiddleware` |
|||
|
|||
Вам нужно импортировать `WSGIMiddleware`. |
|||
|
|||
Затем обернуть WSGI (например, Flask) приложение с помощью middleware (промежуточного слоя). |
|||
|
|||
И затем смонтировать это под path. |
|||
|
|||
{* ../../docs_src/wsgi/tutorial001.py hl[2:3,3] *} |
|||
|
|||
## Проверьте это |
|||
|
|||
Теперь каждый HTTP-запрос по path `/v1/` будет обработан приложением Flask. |
|||
|
|||
А остальные запросы будут обработаны **FastAPI**. |
|||
|
|||
Если вы запустите его и перейдете по ссылке <a href="http://localhost:8000/v1/" class="external-link" target="_blank">http://localhost:8000/v1/</a>, вы увидите ответ от Flask: |
|||
|
|||
```txt |
|||
Hello, World from Flask! |
|||
``` |
|||
|
|||
И если вы перейдете по ссылке <a href="http://localhost:8000/v2" class="external-link" target="_blank">http://localhost:8000/v2</a>, вы увидите ответ от FastAPI: |
|||
|
|||
```JSON |
|||
{ |
|||
"message": "Hello World" |
|||
} |
|||
``` |
@ -0,0 +1,17 @@ |
|||
# Развёртывание FastAPI на облачных провайдерах |
|||
|
|||
Вы можете использовать практически **любого облачного провайдера** для развертывания вашего приложения FastAPI. |
|||
|
|||
В большинстве случаев у основных облачных провайдеров есть руководства по развертыванию FastAPI с их помощью. |
|||
|
|||
## Облачные провайдеры - спонсоры |
|||
|
|||
Некоторые облачные провайдеры ✨ [**спонсируют FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, это обеспечивает продолжительное и здоровое **развитие** FastAPI и его **экосистемы**. |
|||
|
|||
Это демонстрирует их настоящую приверженность FastAPI и его **сообществу** (вам), так как они не только хотят предоставить вам **хороший сервис**, но и хотят убедиться, что у вас есть **хороший и здоровый фреймворк**, FastAPI. 🙇 |
|||
|
|||
Возможно, вам захочется попробовать их услуги и следовать их руководствам: |
|||
|
|||
* <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://docs.render.com/deploy-fastapi?utm_source=deploydoc&utm_medium=referral&utm_campaign=fastapi" class="external-link" target="_blank">Render</a> |
@ -0,0 +1,137 @@ |
|||
# Воркеры сервера - Uvicorn с воркерами |
|||
|
|||
Давайте снова рассмотрим концепции деплоя: |
|||
|
|||
* Безопасность - HTTPS |
|||
* Запуск при старте |
|||
* Перезапуски |
|||
* **Репликация (количество рабочих процессов)** |
|||
* Память |
|||
* Подготовительные шаги перед запуском |
|||
|
|||
До этого момента, с помощью всех туториалов в документации, вы, вероятно, запускали **серверную программу**, например, используя команду `fastapi`, которая запускает Uvicorn, выполняя **единственный процесс**. |
|||
|
|||
При деплое приложений вы, вероятно, захотите иметь некоторую **репликацию процессов**, чтобы воспользоваться преимуществами **многих ядер** и иметь возможность обрабатывать больше запросов. |
|||
|
|||
Как вы видели в предыдущей главе о [Концепции деплоя](concepts.md){.internal-link target=_blank}, существует множество стратегий, которые вы можете использовать. |
|||
|
|||
Здесь я покажу вам, как использовать **Uvicorn** с **воркер-процессами**, используя команду `fastapi` или непосредственно команду `uvicorn`. |
|||
|
|||
/// info | Информация |
|||
|
|||
Если вы используете контейнеры, например, с Docker или Kubernetes, я расскажу вам больше об этом в следующей главе: [FastAPI в контейнерах - Docker](docker.md){.internal-link target=_blank}. |
|||
|
|||
В частности, работая с **Kubernetes**, вы, вероятно, **не** захотите использовать воркеры, а вместо этого запускать **один процесс Uvicorn в контейнере**, но об этом я расскажу позже в той главе. |
|||
|
|||
/// |
|||
|
|||
## Несколько воркеров |
|||
|
|||
Вы можете запустить несколько воркеров, используя опцию командной строки `--workers`: |
|||
|
|||
//// tab | `fastapi` |
|||
|
|||
Если вы используете команду `fastapi`: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ <font color="#4E9A06">fastapi</font> run --workers 4 <u style="text-decoration-style:solid">main.py</u> |
|||
|
|||
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Запуск продакшн-сервера 🚀 |
|||
|
|||
Поиск структуры файлов пакета в директориях с |
|||
<font color="#3465A4">__init__.py</font> |
|||
Импорт из <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font> |
|||
|
|||
<span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> 🐍 main.py |
|||
|
|||
<span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Импортирование объекта приложения FastAPI из модуля со следующим кодом: |
|||
|
|||
<u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u> |
|||
|
|||
<span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Использование строки импорта: <font color="#3465A4">main:app</font> |
|||
|
|||
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Сервер стартовал на <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000</u></font> |
|||
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Документация по адресу <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000/docs</u></font> |
|||
|
|||
Логи: |
|||
|
|||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn работает на <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000</u></font> <b>(</b>Нажмите CTRL+C для выхода<b>)</b> |
|||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Стартовал родительский процесс <b>[</b><font color="#34E2E2"><b>27365</b></font><b>]</b> |
|||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Стартовал серверный процесс <b>[</b><font color="#34E2E2"><b>27368</b></font><b>]</b> |
|||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Стартовал серверный процесс <b>[</b><font color="#34E2E2"><b>27369</b></font><b>]</b> |
|||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Стартовал серверный процесс <b>[</b><font color="#34E2E2"><b>27370</b></font><b>]</b> |
|||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Стартовал серверный процесс <b>[</b><font color="#34E2E2"><b>27367</b></font><b>]</b> |
|||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Ожидание запуска приложения. |
|||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Ожидание запуска приложения. |
|||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Ожидание запуска приложения. |
|||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Ожидание запуска приложения. |
|||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Запуск приложения завершён. |
|||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Запуск приложения завершён. |
|||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Запуск приложения завершён. |
|||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Запуск приложения завершён. |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
//// |
|||
|
|||
//// tab | `uvicorn` |
|||
|
|||
Если вы предпочитаете использовать команду `uvicorn` напрямую: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4 |
|||
<font color="#A6E22E">INFO</font>: Uvicorn работает на <b>http://0.0.0.0:8080</b> (Нажмите CTRL+C для выхода) |
|||
<font color="#A6E22E">INFO</font>: Стартовал родительский процесс [<font color="#A1EFE4"><b>27365</b></font>] |
|||
<font color="#A6E22E">INFO</font>: Стартовал серверный процесс [<font color="#A1EFE4">27368</font>] |
|||
<font color="#A6E22E">INFO</font>: Ожидание запуска приложения. |
|||
<font color="#A6E22E">INFO</font>: Запуск приложения завершён. |
|||
<font color="#A6E22E">INFO</font>: Стартовал серверный процесс [<font color="#A1EFE4">27369</font>] |
|||
<font color="#A6E22E">INFO</font>: Ожидание запуска приложения. |
|||
<font color="#A6E22E">INFO</font>: Запуск приложения завершён. |
|||
<font color="#A6E22E">INFO</font>: Стартовал серверный процесс [<font color="#A1EFE4">27370</font>] |
|||
<font color="#A6E22E">INFO</font>: Ожидание запуска приложения. |
|||
<font color="#A6E22E">INFO</font>: Запуск приложения завершён. |
|||
<font color="#A6E22E">INFO</font>: Стартовал серверный процесс [<font color="#A1EFE4">27367</font>] |
|||
<font color="#A6E22E">INFO</font>: Ожидание запуска приложения. |
|||
<font color="#A6E22E">INFO</font>: Запуск приложения завершён. |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
//// |
|||
|
|||
Единственная новая опция здесь - `--workers`, указывающая Uvicorn запустить 4 воркер-процесса. |
|||
|
|||
Вы также можете увидеть, что отображается **PID** каждого процесса: `27365` для родительского процесса (это **менеджер процессов**) и один для каждого воркер-процесса: `27368`, `27369`, `27370` и `27367`. |
|||
|
|||
## Концепции деплоя |
|||
|
|||
Здесь вы увидели, как использовать несколько **воркеров** для **параллелизации** выполнения приложения, использовать преимущества **многих ядер** процессора и иметь возможность обрабатывать **больше запросов**. |
|||
|
|||
Из списка концепций деплоя, приведённого выше, использование воркеров в основном поможет с **репликацией**, и немного - с **перезапусками**, но вам по-прежнему необходимо позаботиться о следующих вещах: |
|||
|
|||
* **Безопасность - HTTPS** |
|||
* **Запуск при старте** |
|||
* ***Перезапуски*** |
|||
* Репликация (количество рабочих процессов) |
|||
* **Память** |
|||
* **Подготовительные шаги перед запуском** |
|||
|
|||
## Контейнеры и Docker |
|||
|
|||
В следующей главе о [FastAPI в контейнерах - Docker](docker.md){.internal-link target=_blank} я объясню некоторые стратегии, которые вы можете использовать для работы с другими **концепциями деплоя**. |
|||
|
|||
Я покажу вам, как **создать собственный образ с нуля** для запуска одного процесса Uvicorn. Это простой процесс и, вероятно, то, что вы захотите сделать при использовании системы управления распределёнными контейнерами, такой как **Kubernetes**. |
|||
|
|||
## Итог |
|||
|
|||
Вы можете использовать несколько воркер-процессов с опцией `--workers` в CLI с командами `fastapi` или `uvicorn`, чтобы воспользоваться преимуществами **многоядерных процессоров**, для выполнения **нескольких процессов параллельно**. |
|||
|
|||
Вы можете использовать эти инструменты и идеи, если настраиваете **собственную систему деплоя**, одновременно заботясь о других концепциях деплоя самостоятельно. |
|||
|
|||
Изучите следующую главу, чтобы узнать о **FastAPI** с контейнерами (например, Docker и Kubernetes). Вы увидите, что эти инструменты также имеют простые способы решения других **концепций деплоя**. ✨ |
@ -0,0 +1,56 @@ |
|||
# Условный OpenAPI |
|||
|
|||
Если потребуется, вы можете использовать настройки и переменные окружения, чтобы условно настраивать OpenAPI в зависимости от окружения, и даже полностью отключать его. |
|||
|
|||
## О безопасности, API и документации |
|||
|
|||
Скрытие пользовательских интерфейсов вашей документации в продакшне *не должно* быть способом защиты вашего API. |
|||
|
|||
Это не добавляет никакой дополнительной безопасности вашему API, *операции пути* по-прежнему будут доступны там, где они есть. |
|||
|
|||
Если в вашем коде есть уязвимость, она всё равно будет существовать. |
|||
|
|||
Скрытие документации лишь затрудняет понимание того, как взаимодействовать с вашим API, и может усложнить его отладку в продакшне. Это может рассматриваться просто как форма <a href="https://en.wikipedia.org/wiki/Security_through_obscurity" class="external-link" target="_blank">безопасности через неясность</a>. |
|||
|
|||
Если вы хотите защитить ваш API, есть несколько более эффективных подходов: |
|||
|
|||
* Убедитесь, что у вас есть хорошо определённые Pydantic-модели для тел запросов и ответов. |
|||
* Настройте необходимые разрешения и роли с использованием зависимостей. |
|||
* Никогда не храните пароли в открытом виде, только хеши паролей. |
|||
* Реализуйте и используйте известные криптографические инструменты, такие как Passlib и JWT токены и т. д. |
|||
* Добавьте более детальный контроль разрешений с помощью OAuth2 областей, где это необходимо. |
|||
* ...и так далее. |
|||
|
|||
Тем не менее, может быть очень специфический случай, когда вам действительно нужно отключить документацию API для какого-то окружения (например, для продакшна) или в зависимости от настроек из переменных окружения. |
|||
|
|||
## Условный OpenAPI из настроек и переменных окружения |
|||
|
|||
Вы можете легко использовать те же настройки Pydantic для конфигурации вашего сгенерированного OpenAPI и интерфейсов документации. |
|||
|
|||
Например: |
|||
|
|||
{* ../../docs_src/conditional_openapi/tutorial001.py hl[6,11] *} |
|||
|
|||
Здесь мы объявляем настройку `openapi_url` с тем же значением по умолчанию `"/openapi.json"`. |
|||
|
|||
Затем мы используем его при создании приложения `FastAPI`. |
|||
|
|||
Затем вы можете отключить OpenAPI (включая документацию UI) путем установки значения переменной окружения `OPENAPI_URL` в пустую строку, так: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ OPENAPI_URL= uvicorn main:app |
|||
|
|||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
Затем, если вы перейдете по URL-адресам `/openapi.json`, `/docs` или `/redoc`, вы получите ошибку `404 Not Found`, например: |
|||
|
|||
```JSON |
|||
{ |
|||
"detail": "Not Found" |
|||
} |
|||
``` |
@ -0,0 +1,70 @@ |
|||
# Настройка Swagger UI |
|||
|
|||
Вы можете настроить некоторые дополнительные <a href="https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/" class="external-link" target="_blank">параметры Swagger UI</a>. |
|||
|
|||
Чтобы их настроить, передайте аргумент `swagger_ui_parameters` при создании объекта приложения `FastAPI()` или в функцию `get_swagger_ui_html()`. |
|||
|
|||
`swagger_ui_parameters` принимает словарь с конфигурациями, передаваемыми непосредственно в Swagger UI. |
|||
|
|||
FastAPI преобразует конфигурации в **JSON**, чтобы они были совместимы с JavaScript, так как это нужно для Swagger UI. |
|||
|
|||
## Отключение подсветки синтаксиса |
|||
|
|||
Например, вы можете отключить подсветку синтаксиса в Swagger UI. |
|||
|
|||
Без изменения настроек, подсветка синтаксиса включена по умолчанию: |
|||
|
|||
<img src="/img/tutorial/extending-openapi/image02.png"> |
|||
|
|||
Но вы можете отключить её, установив `syntaxHighlight` в `False`: |
|||
|
|||
{* ../../docs_src/configure_swagger_ui/tutorial001.py hl[3] *} |
|||
|
|||
...и тогда Swagger UI больше не будет отображать подсветку синтаксиса: |
|||
|
|||
<img src="/img/tutorial/extending-openapi/image03.png"> |
|||
|
|||
## Изменение темы |
|||
|
|||
Таким же образом вы можете установить тему подсветки синтаксиса с помощью ключа `"syntaxHighlight.theme"` (обратите внимание, что в нем есть точка): |
|||
|
|||
{* ../../docs_src/configure_swagger_ui/tutorial002.py hl[3] *} |
|||
|
|||
Эта конфигурация изменит цветовую тему подсветки синтаксиса: |
|||
|
|||
<img src="/img/tutorial/extending-openapi/image04.png"> |
|||
|
|||
## Изменение параметров Swagger UI по умолчанию |
|||
|
|||
FastAPI включает некоторые параметры конфигурации по умолчанию, подходящие для большинства случаев использования. |
|||
|
|||
Эти конфигурации по умолчанию включают: |
|||
|
|||
{* ../../fastapi/openapi/docs.py ln[8:23] hl[17:23] *} |
|||
|
|||
Вы можете переопределить любой из них, установив другое значение в аргументе `swagger_ui_parameters`. |
|||
|
|||
Например, чтобы отключить `deepLinking`, вы можете передать эти настройки в `swagger_ui_parameters`: |
|||
|
|||
{* ../../docs_src/configure_swagger_ui/tutorial003.py hl[3] *} |
|||
|
|||
## Другие параметры Swagger UI |
|||
|
|||
Чтобы узнать о всех возможных конфигурациях, которые вы можете использовать, прочитайте официальную <a href="https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/" class="external-link" target="_blank">документацию по параметрам Swagger UI</a>. |
|||
|
|||
## Настройки только для JavaScript |
|||
|
|||
Swagger UI также позволяет использовать другие конфигурации, представляющие собой **только для JavaScript** объекты (например, функции JavaScript). |
|||
|
|||
FastAPI также включает эти настройки `presets`, предназначенные только для JavaScript: |
|||
|
|||
```JavaScript |
|||
presets: [ |
|||
SwaggerUIBundle.presets.apis, |
|||
SwaggerUIBundle.SwaggerUIStandalonePreset |
|||
] |
|||
``` |
|||
|
|||
Это объекты **JavaScript**, а не строки, поэтому вы не можете передавать их напрямую из кода Python. |
|||
|
|||
Если вам нужно использовать такие настройки, предназначенные только для JavaScript, вы можете использовать один из методов выше. Переопределите все *операции пути* Swagger UI и вручную напишите любой нужный JavaScript. |
@ -0,0 +1,185 @@ |
|||
# Статические ресурсы пользовательского интерфейса для документации (самостоятельный хостинг) |
|||
|
|||
Документация API использует **Swagger UI** и **ReDoc**, и для каждой из них нужны определенные JavaScript и CSS файлы. |
|||
|
|||
По умолчанию эти файлы обслуживаются с <abbr title="Content Delivery Network: сервис, обычно состоящий из нескольких серверов, который предоставляет статические файлы, такие как JavaScript и CSS. Обычно используется для обслуживания этих файлов с сервера, находящегося ближе к клиенту, улучшая производительность.">CDN</abbr>. |
|||
|
|||
Но можно настроить их по-своему, задать определенный CDN или обслуживать файлы самостоятельно. |
|||
|
|||
## Пользовательский CDN для JavaScript и CSS |
|||
|
|||
Допустим, вы хотите использовать другой <abbr title="Content Delivery Network">CDN</abbr>, например, `https://unpkg.com/`. |
|||
|
|||
Это может быть полезно, если, например, вы живете в стране, где ограничивают некоторые URL-адреса. |
|||
|
|||
### Отключите автоматическую документацию |
|||
|
|||
Первый шаг — отключить автоматическую документацию, так как по умолчанию она использует стандартный CDN. |
|||
|
|||
Чтобы отключить их, установите для их URL-адресов значение `None` при создании приложения `FastAPI`: |
|||
|
|||
{* ../../docs_src/custom_docs_ui/tutorial001.py hl[8] *} |
|||
|
|||
### Включите пользовательскую документацию |
|||
|
|||
Теперь вы можете создать *операции пути* для пользовательской документации. |
|||
|
|||
Вы можете повторно использовать внутренние функции FastAPI для создания HTML-страниц для документации и передать им необходимые аргументы: |
|||
|
|||
* `openapi_url`: URL-адрес, где HTML-страница документации может получить OpenAPI-схему для вашего API. Вы можете использовать здесь атрибут `app.openapi_url`. |
|||
* `title`: заголовок вашего API. |
|||
* `oauth2_redirect_url`: здесь вы можете использовать `app.swagger_ui_oauth2_redirect_url`, чтобы воспользоваться значением по умолчанию. |
|||
* `swagger_js_url`: URL-адрес, по которому HTML для ваших документов Swagger UI может получить **JavaScript** файл. Это пользовательский URL-адрес CDN. |
|||
* `swagger_css_url`: URL-адрес, по которому HTML для ваших документов Swagger UI может получить **CSS** файл. Это пользовательский URL-адрес CDN. |
|||
|
|||
И аналогично для ReDoc... |
|||
|
|||
{* ../../docs_src/custom_docs_ui/tutorial001.py hl[2:6,11:19,22:24,27:33] *} |
|||
|
|||
/// tip | Совет |
|||
|
|||
*Операция пути* для `swagger_ui_redirect` — это вспомогательная функция, когда вы используете OAuth2. |
|||
|
|||
Если вы интегрируете свой API с провайдером OAuth2, вы сможете пройти аутентификацию и вернуться к документации API с полученными учетными данными. И взаимодействовать с ней, используя настоящую аутентификацию OAuth2. |
|||
|
|||
Swagger UI справится с этим закулисно для вас, но ему нужен этот вспомогательный "редирект". |
|||
|
|||
/// |
|||
|
|||
### Создайте *операцию пути* для тестирования |
|||
|
|||
Теперь, чтобы убедиться, что всё работает, создайте *операцию пути*: |
|||
|
|||
{* ../../docs_src/custom_docs_ui/tutorial001.py hl[36:38] *} |
|||
|
|||
### Проверьте это |
|||
|
|||
Теперь вы должны иметь возможность перейти к вашей документации по адресу <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a> и перезагрузить страницу. Она будет загружать эти ресурсы с нового CDN. |
|||
|
|||
## Самостоятельный хостинг JavaScript и CSS для документации |
|||
|
|||
Самостоятельный хостинг JavaScript и CSS может быть полезен, если, например, вам нужно, чтобы ваше приложение продолжало работать даже в офлайн-режиме, без открытого доступа к Интернету, или в локальной сети. |
|||
|
|||
Здесь вы увидите, как можно обслуживать эти файлы самостоятельно, в том же приложении FastAPI, и настроить документацию для их использования. |
|||
|
|||
### Структура файлов проекта |
|||
|
|||
Допустим, структура файлов вашего проекта выглядит так: |
|||
|
|||
``` |
|||
. |
|||
├── app |
|||
│ ├── __init__.py |
|||
│ ├── main.py |
|||
``` |
|||
|
|||
Теперь создайте каталог для хранения этих статических файлов. |
|||
|
|||
Теперь ваша новая структура файлов может выглядеть так: |
|||
|
|||
``` |
|||
. |
|||
├── app |
|||
│ ├── __init__.py |
|||
│ ├── main.py |
|||
└── static/ |
|||
``` |
|||
|
|||
### Загрузите файлы |
|||
|
|||
Загрузите необходимые статические файлы для документации и разместите их в каталоге `static/`. |
|||
|
|||
Вы можете щелкнуть правой кнопкой мыши по каждой ссылке и выбрать опцию, аналогичную `Сохранить ссылку как...`. |
|||
|
|||
**Swagger UI** использует файлы: |
|||
|
|||
* <a href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js" class="external-link" target="_blank">`swagger-ui-bundle.js`</a> |
|||
* <a href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css" class="external-link" target="_blank">`swagger-ui.css`</a> |
|||
|
|||
А **ReDoc** использует файл: |
|||
|
|||
* <a href="https://cdn.jsdelivr.net/npm/redoc@2/bundles/redoc.standalone.js" class="external-link" target="_blank">`redoc.standalone.js`</a> |
|||
|
|||
После этого ваша структура файлов может выглядеть следующим образом: |
|||
|
|||
``` |
|||
. |
|||
├── app |
|||
│ ├── __init__.py |
|||
│ ├── main.py |
|||
└── static |
|||
├── redoc.standalone.js |
|||
├── swagger-ui-bundle.js |
|||
└── swagger-ui.css |
|||
``` |
|||
|
|||
### Обслуживание статических файлов |
|||
|
|||
* Импортируйте `StaticFiles`. |
|||
* "Монтируйте" экземпляр `StaticFiles()` на конкретном пути. |
|||
|
|||
{* ../../docs_src/custom_docs_ui/tutorial002.py hl[7,11] *} |
|||
|
|||
### Тестирование статических файлов |
|||
|
|||
Запустите ваше приложение и перейдите на <a href="http://127.0.0.1:8000/static/redoc.standalone.js" class="external-link" target="_blank">http://127.0.0.1:8000/static/redoc.standalone.js</a>. |
|||
|
|||
Вы должны увидеть очень длинный JavaScript файл для **ReDoc**. |
|||
|
|||
Он может начинаться с чего-то вроде: |
|||
|
|||
```JavaScript |
|||
/*! Для информации о лицензии смотрите redoc.standalone.js.LICENSE.txt */ |
|||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("null")): |
|||
... |
|||
``` |
|||
|
|||
Это подтверждает, что вы можете обслуживать статические файлы из вашего приложения, и что вы разместили статические файлы для документации в правильном месте. |
|||
|
|||
Теперь мы можем настроить приложение для использования этих статических файлов для документации. |
|||
|
|||
### Отключите автоматическую документацию для статических файлов |
|||
|
|||
Также как при использовании пользовательского CDN, первый шаг — отключить автоматическую документацию, так как по умолчанию они используют CDN. |
|||
|
|||
Чтобы отключить их, установите для их URL-адресов значение `None` при создании приложения `FastAPI`: |
|||
|
|||
{* ../../docs_src/custom_docs_ui/tutorial002.py hl[9] *} |
|||
|
|||
### Включите пользовательскую документацию для статических файлов |
|||
|
|||
И аналогично предыдущим шагам с пользовательским CDN, теперь вы можете создать *операции пути* для пользовательской документации. |
|||
|
|||
Опять же, вы можете повторно использовать внутренние функции FastAPI для создания HTML-страниц для документации и передать им необходимые аргументы: |
|||
|
|||
* `openapi_url`: URL-адрес, где HTML-страница документации может получить OpenAPI-схему для вашего API. Вы можете использовать здесь атрибут `app.openapi_url`. |
|||
* `title`: заголовок вашего API. |
|||
* `oauth2_redirect_url`: здесь вы можете использовать `app.swagger_ui_oauth2_redirect_url`, чтобы воспользоваться значением по умолчанию. |
|||
* `swagger_js_url`: URL-адрес, по которому HTML для ваших документов Swagger UI может получить **JavaScript** файл. **Это тот файл, который теперь обслуживает ваше приложение**. |
|||
* `swagger_css_url`: URL-адрес, по которому HTML для ваших документов Swagger UI может получить **CSS** файл. **Это тот файл, который теперь обслуживает ваше приложение**. |
|||
|
|||
И аналогично для ReDoc... |
|||
|
|||
{* ../../docs_src/custom_docs_ui/tutorial002.py hl[2:6,14:22,25:27,30:36] *} |
|||
|
|||
/// tip | Совет |
|||
|
|||
*Операция пути* для `swagger_ui_redirect` — это вспомогательная функция, когда вы используете OAuth2. |
|||
|
|||
Если вы интегрируете свой API с провайдером OAuth2, вы сможете пройти аутентификацию и вернуться к документации API с полученными учетными данными. И взаимодействовать с ней, используя настоящую аутентификацию OAuth2. |
|||
|
|||
Swagger UI справится с этим закулисно для вас, но ему нужен этот вспомогательный "редирект". |
|||
|
|||
/// |
|||
|
|||
### Создайте *операцию пути* для тестирования статических файлов |
|||
|
|||
Теперь, чтобы убедиться, что всё работает, создайте *операцию пути*: |
|||
|
|||
{* ../../docs_src/custom_docs_ui/tutorial002.py hl[39:41] *} |
|||
|
|||
### Проверьте пользовательский интерфейс статических файлов |
|||
|
|||
Теперь вы должны иметь возможность отключить WiFi, перейти к вашей документации по адресу <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a> и перезагрузить страницу. |
|||
|
|||
И даже без Интернета вы сможете увидеть документацию для вашего API и взаимодействовать с ней. |
@ -0,0 +1,109 @@ |
|||
# Пользовательские классы Request и APIRoute |
|||
|
|||
В некоторых случаях вам может понадобиться переопределить логику, используемую в классах `Request` и `APIRoute`. |
|||
|
|||
В частности, это может быть хорошей альтернативой логике в middleware (Промежуточный слой). |
|||
|
|||
Например, если вы хотите прочитать или изменить тело запроса до того, как оно будет обработано вашим приложением. |
|||
|
|||
/// danger |
|||
|
|||
Это "продвинутая" функция. |
|||
|
|||
Если вы только начинаете работать с **FastAPI**, вы можете пропустить этот раздел. |
|||
|
|||
/// |
|||
|
|||
## Сценарии использования |
|||
|
|||
Некоторые сценарии использования включают: |
|||
|
|||
* Конвертация тел запросов, не являющихся JSON, в JSON (например, <a href="https://msgpack.org/index.html" class="external-link" target="_blank">`msgpack`</a>). |
|||
* Разжатие тел запросов, сжатых методом gzip. |
|||
* Автоматическое логирование всех тел запросов. |
|||
|
|||
## Обработка пользовательских кодировок тела запроса |
|||
|
|||
Давайте посмотрим, как использовать пользовательский подкласс `Request` для разжатия gzip-запросов. |
|||
|
|||
И подкласс `APIRoute`, чтобы использовать этот пользовательский класс запроса. |
|||
|
|||
### Создание пользовательского класса `GzipRequest` |
|||
|
|||
/// tip | Совет |
|||
|
|||
Это учебный пример, чтобы показать как это работает. Если вам нужна поддержка gzip, вы можете использовать предоставленный [`GzipMiddleware`](../advanced/middleware.md#gzipmiddleware){.internal-link target=_blank}. |
|||
|
|||
/// |
|||
|
|||
Сначала мы создадим класс `GzipRequest`, который переопределит метод `Request.body()`, чтобы разжать тело запроса, если присутствует соответствующий заголовок. |
|||
|
|||
Если в заголовке нет `gzip`, он не будет пытаться разжать тело запроса. |
|||
|
|||
Таким образом, один и тот же класс маршрута может обрабатывать как gzip-сжатые, так и несжатые запросы. |
|||
|
|||
{* ../../docs_src/custom_request_and_route/tutorial001.py hl[8:15] *} |
|||
|
|||
### Создание пользовательского класса `GzipRoute` |
|||
|
|||
Затем мы создаем пользовательский подкласс `fastapi.routing.APIRoute`, который будет использовать `GzipRequest`. |
|||
|
|||
На этот раз он переопределит метод `APIRoute.get_route_handler()`. |
|||
|
|||
Этот метод возвращает функцию. И эта функция принимает запрос и возвращает ответ. |
|||
|
|||
Здесь мы используем его для создания `GzipRequest` из исходного запроса. |
|||
|
|||
{* ../../docs_src/custom_request_and_route/tutorial001.py hl[18:26] *} |
|||
|
|||
/// note | Технические детали |
|||
|
|||
`Request` имеет атрибут `request.scope`, который является просто Python-словарем, содержащим метаданные, относящиеся к запросу. |
|||
|
|||
`Request` также имеет `request.receive`, это функция для "получения" тела запроса. |
|||
|
|||
Словарь `scope` и функция `receive` являются частью спецификации ASGI. |
|||
|
|||
И именно эти две вещи, `scope` и `receive`, нужны для создания нового экземпляра `Request`. |
|||
|
|||
Чтобы узнать больше о `Request`, ознакомьтесь с <a href="https://www.starlette.io/requests/" class="external-link" target="_blank">документацией Starlette о запросах</a>. |
|||
|
|||
/// |
|||
|
|||
Единственное, что функция, возвращаемая `GzipRequest.get_route_handler`, делает иначе, это преобразует `Request` в `GzipRequest`. |
|||
|
|||
Таким образом, наш `GzipRequest` позаботится о разжатии данных (если это необходимо) перед передачей их нашим *операциям пути*. |
|||
|
|||
После этого вся логика обработки такая же. |
|||
|
|||
Но благодаря изменениям в `GzipRequest.body`, тело запроса будет автоматически разжиматься, когда **FastAPI** нужно будет его загрузить. |
|||
|
|||
## Доступ к телу запроса в обработчике исключений |
|||
|
|||
/// tip | Подсказка |
|||
|
|||
Чтобы решить эту же проблему, вероятно, гораздо проще использовать `body` в пользовательском обработчике для `RequestValidationError` ([Обработка ошибок](../tutorial/handling-errors.md#use-the-requestvalidationerror-body){.internal-link target=_blank}). |
|||
|
|||
Но этот пример по-прежнему актуален и показывает, как взаимодействовать с внутренними компонентами. |
|||
|
|||
/// |
|||
|
|||
Мы также можем использовать этот же подход, чтобы получить доступ к телу запроса в обработчике исключений. |
|||
|
|||
Все, что нам нужно сделать, — это обработать запрос внутри блока `try`/`except`: |
|||
|
|||
{* ../../docs_src/custom_request_and_route/tutorial002.py hl[13,15] *} |
|||
|
|||
Если возникает исключение, экземпляр `Request` все равно будет доступен, поэтому мы можем прочитать и использовать тело запроса при обработке ошибки: |
|||
|
|||
{* ../../docs_src/custom_request_and_route/tutorial002.py hl[16:18] *} |
|||
|
|||
## Пользовательский класс `APIRoute` в роутере |
|||
|
|||
Вы также можете установить параметр `route_class` для `APIRouter`: |
|||
|
|||
{* ../../docs_src/custom_request_and_route/tutorial003.py hl[26] *} |
|||
|
|||
В этом примере *операции путей* под `router` будут использовать пользовательский класс `TimedRoute`, и в ответе будет добавлен дополнительный HTTP-заголовок `X-Response-Time` с временем, затраченным на генерацию ответа: |
|||
|
|||
{* ../../docs_src/custom_request_and_route/tutorial003.py hl[13:20] *} |
@ -0,0 +1,80 @@ |
|||
# Расширение OpenAPI |
|||
|
|||
Бывают случаи, когда вам может понадобиться изменить сгенерированную схему OpenAPI. |
|||
|
|||
В этом разделе вы узнаете, как это сделать. |
|||
|
|||
## Обычный процесс |
|||
|
|||
Обычный (по умолчанию) процесс выглядит следующим образом. |
|||
|
|||
Приложение `FastAPI` (экземпляр) имеет метод `.openapi()`, который ожидается вернуть схему OpenAPI. |
|||
|
|||
В рамках создания объекта приложения для `/openapi.json` (или для того, что вы указали в `openapi_url`) регистрируется *операция пути*. |
|||
|
|||
Она просто возвращает JSON ответ с результатом метода `.openapi()` приложения. |
|||
|
|||
По умолчанию метод `.openapi()` проверяет свойство `.openapi_schema`, чтобы увидеть, есть ли в нем содержимое, и возвращает их. |
|||
|
|||
Если его нет, он генерирует его с использованием вспомогательной функции в `fastapi.openapi.utils.get_openapi`. |
|||
|
|||
И эта функция `get_openapi()` принимает в качестве параметров: |
|||
|
|||
* `title`: Заголовок OpenAPI, отображаемый в документации. |
|||
* `version`: Версия вашего API, например `2.5.0`. |
|||
* `openapi_version`: Версия спецификации OpenAPI, используемой. По умолчанию последняя: `3.1.0`. |
|||
* `summary`: Краткое резюме API. |
|||
* `description`: Описание вашего API, оно может включать markdown и будет показано в документации. |
|||
* `routes`: Список маршрутов, это каждая из зарегистрированных *операций пути*. Они берутся из `app.routes`. |
|||
|
|||
/// info | Информация |
|||
|
|||
Параметр `summary` доступен в OpenAPI 3.1.0 и выше, поддерживается FastAPI 0.99.0 и выше. |
|||
|
|||
/// |
|||
|
|||
## Переопределение значений по умолчанию |
|||
|
|||
Используя информацию выше, вы можете использовать ту же вспомогательную функцию для генерации схемы OpenAPI и переопределения каждой части, которая вам нужна. |
|||
|
|||
Например, давайте добавим <a href="https://github.com/Rebilly/ReDoc/blob/master/docs/redoc-vendor-extensions.md#x-logo" class="external-link" target="_blank">OpenAPI расширение ReDoc для включения настраиваемого логотипа</a>. |
|||
|
|||
### Обычный **FastAPI** |
|||
|
|||
Сначала напишите все ваше приложение на **FastAPI** как обычно: |
|||
|
|||
{* ../../docs_src/extending_openapi/tutorial001.py hl[1,4,7:9] *} |
|||
|
|||
### Генерация схемы OpenAPI |
|||
|
|||
Затем используйте ту же вспомогательную функцию для генерации схемы OpenAPI внутри функции `custom_openapi()`: |
|||
|
|||
{* ../../docs_src/extending_openapi/tutorial001.py hl[2,15:21] *} |
|||
|
|||
### Изменение схемы OpenAPI |
|||
|
|||
Теперь вы можете добавить расширение ReDoc, добавив настраиваемый `x-logo` в "объект" `info` в схеме OpenAPI: |
|||
|
|||
{* ../../docs_src/extending_openapi/tutorial001.py hl[22:24] *} |
|||
|
|||
### Кэширование схемы OpenAPI |
|||
|
|||
Вы можете использовать свойство `.openapi_schema` как "кэш", чтобы сохранить вашу сгенерированную схему. |
|||
|
|||
Таким образом, ваше приложение не будет генерировать схему каждый раз, когда пользователь открывает вашу документацию API. |
|||
|
|||
Она будет сгенерирована только один раз, и затем та же самая кэшированная схема будет использована для следующих запросов. |
|||
|
|||
{* ../../docs_src/extending_openapi/tutorial001.py hl[13:14,25:26] *} |
|||
|
|||
### Переопределение метода |
|||
|
|||
Теперь вы можете заменить метод `.openapi()` на вашу новую функцию. |
|||
|
|||
{* ../../docs_src/extending_openapi/tutorial001.py hl[29] *} |
|||
|
|||
### Проверьте это |
|||
|
|||
Как только вы перейдете по адресу <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>, вы увидите, что вы используете ваш настраиваемый логотип (в этом примере используется логотип **FastAPI**): |
|||
|
|||
<img src="/img/tutorial/extending-openapi/image01.png"> |
@ -0,0 +1,39 @@ |
|||
# Общие сведения - How To - Рецепты |
|||
|
|||
Вот несколько путеводителей на другие части документации для общих или часто задаваемых вопросов. |
|||
|
|||
## Фильтрация данных - Безопасность |
|||
|
|||
Чтобы убедиться, что вы не возвращаете больше данных, чем следует, прочитайте документацию по [Учебнику - Модель ответа - Возвращаемый тип](../tutorial/response-model.md){.internal-link target=_blank}. |
|||
|
|||
## Теги документации - OpenAPI |
|||
|
|||
Чтобы добавить теги к вашим *операциям пути* и сгруппировать их в интерфейсе документации, прочитайте документацию по [Учебнику - Конфигурации операций пути - Теги](../tutorial/path-operation-configuration.md#tags){.internal-link target=_blank}. |
|||
|
|||
## Краткое описание и описание документации - OpenAPI |
|||
|
|||
Чтобы добавить краткое описание и описание к вашим *операциям пути* и отобразить их в интерфейсе документации, прочитайте документацию по [Учебнику - Конфигурации операций пути - Краткое описание и описание](../tutorial/path-operation-configuration.md#summary-and-description){.internal-link target=_blank}. |
|||
|
|||
## Описание ответа в документации - OpenAPI |
|||
|
|||
Чтобы определить описание ответа, отображаемого в интерфейсе документации, прочитайте документацию по [Учебнику - Конфигурации операций пути - Описание ответа](../tutorial/path-operation-configuration.md#response-description){.internal-link target=_blank}. |
|||
|
|||
## Устаревание *операции пути* в документации - OpenAPI |
|||
|
|||
Чтобы пометить *операцию пути* как устаревшую и отобразить это в интерфейсе документации, прочитайте документацию по [Учебнику - Конфигурации операций пути - Устаревание](../tutorial/path-operation-configuration.md#deprecate-a-path-operation){.internal-link target=_blank}. |
|||
|
|||
## Преобразование любых данных в совместимые с JSON |
|||
|
|||
Чтобы преобразовать любые данные в совместимые с JSON, прочитайте документацию по [Учебнику - Совместимый кодировщик JSON](../tutorial/encoder.md){.internal-link target=_blank}. |
|||
|
|||
## Метаданные OpenAPI - Документация |
|||
|
|||
Чтобы добавить метаданные в вашу схему OpenAPI, включая лицензию, версию, контактную информацию и т.д., прочитайте документацию по [Учебнику - Метаданные и URL-адреса документации](../tutorial/metadata.md){.internal-link target=_blank}. |
|||
|
|||
## Пользовательский URL OpenAPI |
|||
|
|||
Чтобы кастомизировать (или удалить) URL OpenAPI, прочитайте документацию по [Учебнику - Метаданные и URL-адреса документации](../tutorial/metadata.md#openapi-url){.internal-link target=_blank}. |
|||
|
|||
## URL-адреса документации OpenAPI |
|||
|
|||
Чтобы обновить URL-адреса, используемые для автоматически сгенерированных интерфейсов документации, прочитайте документацию по [Учебнику - Метаданные и URL-адреса документации](../tutorial/metadata.md#docs-urls){.internal-link target=_blank}. |
@ -0,0 +1,60 @@ |
|||
# GraphQL |
|||
|
|||
Так как **FastAPI** основан на стандарте **ASGI**, очень легко интегрировать любую библиотеку **GraphQL**, также совместимую с ASGI. |
|||
|
|||
Вы можете комбинировать обычные *операции пути* FastAPI с GraphQL в одном и том же приложении. |
|||
|
|||
/// tip | Совет |
|||
|
|||
**GraphQL** решает некоторые очень специфические случаи использования. |
|||
|
|||
У него есть **преимущества** и **недостатки** по сравнению с обычными **веб API**. |
|||
|
|||
Убедитесь, что вы оценили, компенсируют ли **выгоды** для вашего случая использования **недостатки**. 🤓 |
|||
|
|||
/// |
|||
|
|||
## Библиотеки GraphQL |
|||
|
|||
Вот некоторые из библиотек **GraphQL**, которые поддерживают **ASGI**. Вы можете использовать их с **FastAPI**: |
|||
|
|||
* <a href="https://strawberry.rocks/" class="external-link" target="_blank">Strawberry</a> 🍓 |
|||
* С <a href="https://strawberry.rocks/docs/integrations/fastapi" class="external-link" target="_blank">документацией для FastAPI</a> |
|||
* <a href="https://ariadnegraphql.org/" class="external-link" target="_blank">Ariadne</a> |
|||
* С <a href="https://ariadnegraphql.org/docs/fastapi-integration" class="external-link" target="_blank">документацией для FastAPI</a> |
|||
* <a href="https://tartiflette.io/" class="external-link" target="_blank">Tartiflette</a> |
|||
* С <a href="https://tartiflette.github.io/tartiflette-asgi/" class="external-link" target="_blank">Tartiflette ASGI</a> для обеспечения интеграции с ASGI |
|||
* <a href="https://graphene-python.org/" class="external-link" target="_blank">Graphene</a> |
|||
* С <a href="https://github.com/ciscorn/starlette-graphene3" class="external-link" target="_blank">starlette-graphene3</a> |
|||
|
|||
## GraphQL с использованием Strawberry |
|||
|
|||
Если вам нужно или вы хотите работать с **GraphQL**, библиотека <a href="https://strawberry.rocks/" class="external-link" target="_blank">**Strawberry**</a> является **рекомендуемой**, так как ее дизайн наиболее близок к дизайну **FastAPI**, и всё основано на **аннотациях типов**. |
|||
|
|||
В зависимости от вашего случая использования, вы можете предпочесть другую библиотеку, но если бы вы спросили меня, я бы, вероятно, предложил попробовать **Strawberry**. |
|||
|
|||
Вот небольшой пример того, как можно интегрировать Strawberry с FastAPI: |
|||
|
|||
{* ../../docs_src/graphql/tutorial001.py hl[3,22,25] *} |
|||
|
|||
Вы можете узнать больше о Strawberry в <a href="https://strawberry.rocks/" class="external-link" target="_blank">документации Strawberry</a>. |
|||
|
|||
А также документацию о <a href="https://strawberry.rocks/docs/integrations/fastapi" class="external-link" target="_blank">Strawberry с FastAPI</a>. |
|||
|
|||
## Устаревший `GraphQLApp` от Starlette |
|||
|
|||
В предыдущих версиях Starlette включалась класс `GraphQLApp` для интеграции с <a href="https://graphene-python.org/" class="external-link" target="_blank">Graphene</a>. |
|||
|
|||
Он был исключен из Starlette, но если у вас есть код, который использует его, вы можете легко **мигрировать** на <a href="https://github.com/ciscorn/starlette-graphene3" class="external-link" target="_blank">starlette-graphene3</a>, который покрывает тот же случай использования и имеет **почти идентичный интерфейс**. |
|||
|
|||
/// tip | Совет |
|||
|
|||
Если вам нужен GraphQL, я всё же рекомендую обратить внимание на <a href="https://strawberry.rocks/" class="external-link" target="_blank">Strawberry</a>, так как он основан на аннотациях типов, а не на кастомных классах и типах. |
|||
|
|||
/// |
|||
|
|||
## Узнать больше |
|||
|
|||
Вы можете узнать больше о **GraphQL** в <a href="https://graphql.org/" class="external-link" target="_blank">официальной документации GraphQL</a>. |
|||
|
|||
Также вы можете прочитать больше о каждой из описанных выше библиотек по предоставленным ссылкам. |
@ -0,0 +1,13 @@ |
|||
# Как сделать - Рецепты |
|||
|
|||
Здесь вы найдете различные рецепты или руководства "как сделать" для **различных тем**. |
|||
|
|||
Большинство этих идей будут более или менее **независимыми**, и в большинстве случаев вам следует изучать их только в том случае, если они напрямую применимы к **вашему проекту**. |
|||
|
|||
Если что-то кажется интересным и полезным для вашего проекта, смело изучайте это, в противном случае вы можете просто пропустить их. |
|||
|
|||
/// tip | Совет |
|||
|
|||
Если вы хотите **изучить FastAPI** структурированным образом (рекомендуется), прочитайте [Учебник - Руководство пользователя](../tutorial/index.md){.internal-link target=_blank} главу за главой. |
|||
|
|||
/// |
@ -0,0 +1,104 @@ |
|||
# Отдельные схемы OpenAPI для ввода и вывода или нет |
|||
|
|||
При использовании **Pydantic v2** сгенерированный OpenAPI более точный и **правильный**, чем раньше. 😎 |
|||
|
|||
Фактически, в некоторых случаях, будет даже **две схемы JSON** в OpenAPI для одной и той же модели Pydantic: для ввода и вывода, в зависимости от наличия **значений по умолчанию**. |
|||
|
|||
Давайте посмотрим, как это работает и как изменить это, если необходимо. |
|||
|
|||
## Модели Pydantic для ввода и вывода |
|||
|
|||
Предположим, у вас есть модель Pydantic со значениями по умолчанию, как эта: |
|||
|
|||
{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:7] hl[7] *} |
|||
|
|||
### Модель для ввода |
|||
|
|||
Если использовать эту модель для ввода, как здесь: |
|||
|
|||
{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:15] hl[14] *} |
|||
|
|||
...то поле `description` **не будет обязательным**. Потому что у него есть значение по умолчанию `None`. |
|||
|
|||
### Модель ввода в документации |
|||
|
|||
Вы можете убедиться в этом, посмотрев документацию: поле `description` не помечено как обязательное **красной звездочкой**: |
|||
|
|||
<div class="screenshot"> |
|||
<img src="/img/tutorial/separate-openapi-schemas/image01.png"> |
|||
</div> |
|||
|
|||
### Модель для вывода |
|||
|
|||
Но если использовать ту же модель для вывода, как здесь: |
|||
|
|||
{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py hl[19] *} |
|||
|
|||
...тогда, поскольку `description` имеет значение по умолчанию, если вы **ничего не вернете** для этого поля, оно по-прежнему будет иметь это **значение по умолчанию**. |
|||
|
|||
### Модель для данных ответа |
|||
|
|||
Если вы взаимодействуете с документацией и проверяете ответ, даже если в коде ничего не добавлялось в одно из полей `description`, JSON-ответ содержит значение по умолчанию (`null`): |
|||
|
|||
<div class="screenshot"> |
|||
<img src="/img/tutorial/separate-openapi-schemas/image02.png"> |
|||
</div> |
|||
|
|||
Это означает, что у него **всегда будет значение**, просто иногда это значение может быть `None` (или `null` в JSON). |
|||
|
|||
Это значит, что клиенты, использующие ваш API, не должны проверять, существует ли значение, они могут **предполагать, что поле всегда будет**, но в некоторых случаях оно будет иметь значение по умолчанию `None`. |
|||
|
|||
Способ описать это в OpenAPI — отметить это поле как **обязательное**, потому что оно всегда будет присутствовать. |
|||
|
|||
Из-за этого, схема JSON для модели может отличаться в зависимости от того, используется она для **ввода или вывода**: |
|||
|
|||
* для **ввода** `description` **не будет обязательным** |
|||
* для **вывода** оно будет **обязательным** (и возможно `None`, или в терминах JSON, `null`) |
|||
|
|||
### Модель для вывода в документации |
|||
|
|||
Вы также можете проверить модель вывода в документации, **оба** поля `name` и `description` отмечены как **обязательные** с **красной звездочкой**: |
|||
|
|||
<div class="screenshot"> |
|||
<img src="/img/tutorial/separate-openapi-schemas/image03.png"> |
|||
</div> |
|||
|
|||
### Модель для ввода и вывода в документации |
|||
|
|||
И если вы проверите все доступные схемы (JSON схемы) в OpenAPI, вы увидите, что их две: `Item-Input` и `Item-Output`. |
|||
|
|||
Для `Item-Input` `description` **не обязателен**, у него нет красной звездочки. |
|||
|
|||
Но для `Item-Output` `description` **обязателен**, у него есть красная звездочка. |
|||
|
|||
<div class="screenshot"> |
|||
<img src="/img/tutorial/separate-openapi-schemas/image04.png"> |
|||
</div> |
|||
|
|||
С этой функцией из **Pydantic v2**, ваша документация API более **точная**, и если у вас есть автоматически сгенерированные клиенты и SDK, они будут более точными тоже, с более лучшим **опытом разработчика** и консистентностью. 🎉 |
|||
|
|||
## Не разделять схемы |
|||
|
|||
Теперь, есть случаи, когда вы можете захотеть иметь **одну и ту же схему для ввода и вывода**. |
|||
|
|||
Возможно, основным случаем использования будет, если у вас уже есть какой-то автоматически сгенерированный клиентский код/SDK, и вы не хотите обновлять весь автоматически сгенерированный клиентский код/SDK еще. Вы, вероятно, захотите сделать это в какой-то момент, но, возможно, не прямо сейчас. |
|||
|
|||
В этом случае вы можете отключить эту функцию в **FastAPI** с параметром `separate_input_output_schemas=False`. |
|||
|
|||
/// info | Информация |
|||
|
|||
Поддержка `separate_input_output_schemas` была добавлена в FastAPI в версии `0.102.0`. 🤓 |
|||
|
|||
/// |
|||
|
|||
{* ../../docs_src/separate_openapi_schemas/tutorial002_py310.py hl[10] *} |
|||
|
|||
### Одинаковая схема для вводных и выводных моделей в документации |
|||
|
|||
И теперь будет одна единственная схема для ввода и вывода для модели, только `Item`, и `description` будет **необязательным**: |
|||
|
|||
<div class="screenshot"> |
|||
<img src="/img/tutorial/separate-openapi-schemas/image05.png"> |
|||
</div> |
|||
|
|||
Это такое же поведение, как в Pydantic v1. 🤓 |
@ -0,0 +1,7 @@ |
|||
# Тестирование базы данных |
|||
|
|||
Вы можете изучить базы данных, SQL и SQLModel в <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">документации SQLModel</a>. 🤓 |
|||
|
|||
Есть мини <a href="https://sqlmodel.tiangolo.com/tutorial/fastapi/" class="external-link" target="_blank">учебное пособие по использованию SQLModel с FastAPI</a>. ✨ |
|||
|
|||
Этот учебник включает в себя раздел о <a href="https://sqlmodel.tiangolo.com/tutorial/fastapi/tests/" class="external-link" target="_blank">тестировании SQL баз данных</a>. 😎 |
@ -0,0 +1,3 @@ |
|||
# Ресурсы |
|||
|
|||
Дополнительные ресурсы, внешние ссылки, статьи и другое. ✈️ |
Loading…
Reference in new issue