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