Если вы только начинаете с **FastAPI**, вам это может не понадобиться.
Если вы только начинаете работать с **FastAPI**, вам это может не понадобиться.
///
Вы можете объявить дополнительные ответы с дополнительными статус-кодами, медиа-типами, описаниями и т.д.
Вы можете объявлять дополнительные ответы с дополнительными статус-кодами, типами содержимого, описаниями и т.д.
Эти дополнительные ответы будут включены в OpenAPI схему, и они также будут отображаться в документации API.
Эти дополнительные ответы будут включены в схему OpenAPI, так что они также появятся в документации API.
Но для этих дополнительных ответов вы должны убедиться, что возвращаете `Response`, например `JSONResponse`, напрямую с вашим статус-кодом и содержимым.
Но для этих дополнительных ответов вы должны убедиться, что возвращаете `Response`, например,`JSONResponse` напрямую, с вашим статус-кодом и содержимым.
## Дополнительный ответ с `model`
Вы можете передать вашим *декораторам операций пути* параметр `responses`.
Вы можете передать своим декораторам *операций пути* параметр `responses`.
Он принимает `dict`: ключи - это статус-коды для каждого ответа (такие как `200`), а значения - другие `dict` с информацией для каждого из них.
Он принимает `dict`: ключами являются статус-коды для каждого ответа (например, `200`), а значениями являются другие `dict` с информацией для каждого из них.
Каждый из этих `dict` ответов может иметь ключ `model`, содержащий Pydantic модель, так же как и `response_model`.
Каждый из этих `dict` ответа может содержать ключ `model`, содержащий Pydantic-модель, как и `response_model`.
**FastAPI** возьмет эту модель, сгенерирует ее JSON Schema и включит в нужное место в OpenAPI.
Например, чтобы объявить другой ответ с кодом состояния `404` и Pydantic моделью `Message`, можно написать:
Например, чтобы объявить другой ответ с кодом состояния `404` и Pydantic-моделью `Message`, вы можете написать:
Имейте в виду, что вы должны вернуть `JSONResponse` напрямую.
@ -38,14 +38,14 @@
Ключ `model` не является частью OpenAPI.
**FastAPI** возьмет Pydantic модель оттуда, сгенерирует JSON Schema и разместит ее в нужном месте.
**FastAPI** возьмет Pydantic-модель оттуда, сгенерирует JSON Schema и поместит в нужное место.
Правильное место:
* В ключе `content`, который имеет значение другого JSON объекта (`dict`), содержащего:
* Ключ с медиа-типом, например,`application/json`, который содержит как значение другой JSON объект, который содержит:
* Ключ `schema`, который имеет как значение JSON Schema из модели, вот это правильное место.
* **FastAPI** добавляет ссылку здесь на глобальные JSON Schema в другом месте вашего OpenAPI вместо того, чтобы включать её напрямую. Таким образом, другие приложения и клиенты могут использовать эти JSON Schema напрямую, предоставлять лучшие инструменты для генерации кода и т.д.
* В ключе `content`, который имеет значение другой JSON объект (`dict`), содержащий:
* Ключ с типом содержимого, например `application/json`, который в качестве значения содержит другой JSON объект, содержащий:
* Ключ `schema`, который имеет в качестве значения JSON Schema из модели, вот куда нужно поместить.
* **FastAPI** добавляет сюда ссылку на глобальные JSON Schemas в другом месте вашего OpenAPI вместо того, чтобы включать его напрямую. Таким образом, другие приложения и клиенты могут использовать эти JSON Schemas напрямую, предоставлять лучшие инструменты генерации кода и другие функции.
///
@ -55,7 +55,7 @@
{
"responses": {
"404": {
"description": "Additional Response",
"description": "Дополнительный ответ",
"content": {
"application/json": {
"schema": {
@ -65,7 +65,7 @@
}
},
"200": {
"description": "Successful Response",
"description": "Успешный ответ",
"content": {
"application/json": {
"schema": {
@ -75,7 +75,7 @@
}
},
"422": {
"description": "Validation Error",
"description": "Ошибка валидации",
"content": {
"application/json": {
"schema": {
@ -88,75 +88,75 @@
}
```
Схемы ссылаются на другое место внутри OpenAPI схемы:
Схемы ссылаются на другое место внутри схемы OpenAPI:
```JSON hl_lines="4-16"
{
"components": {
"schemas": {
"Message": {
"title": "Message",
"title": "Сообщение",
"required": [
"message"
"сообщение"
],
"type": "object",
"properties": {
"message": {
"title": "Message",
"сообщение": {
"title": "Сообщение",
"type": "string"
}
}
},
"Item": {
"title": "Item",
"title": "Предмет",
"required": [
"id",
"value"
"идентификатор",
"значение"
],
"type": "object",
"properties": {
"id": {
"title": "Id",
"идентификатор": {
"title": "Идентификатор",
"type": "string"
},
"value": {
"title": "Value",
"значение": {
"title": "Значение",
"type": "string"
}
}
},
"ValidationError": {
"title": "ValidationError",
"title": "Ошибка валидации",
"required": [
"loc",
"msg",
"type"
"местоположение",
"сообщение",
"тип"
],
"type": "object",
"properties": {
"loc": {
"местоположение": {
"title": "Location",
"type": "array",
"items": {
"type": "string"
}
},
"msg": {
"title": "Message",
"сообщение": {
"title": "Сообщение",
"type": "string"
},
"type": {
"title": "Error Type",
"тип": {
"title": "Тип ошибки",
"type": "string"
}
}
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"title": "Ошибка валидации HTTP",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"детали": {
"title": "Детали",
"type": "array",
"items": {
"$ref": "#/components/schemas/ValidationError"
@ -169,51 +169,51 @@
}
```
## Дополнительные медиа-типы для основного ответа
## Дополнительные типы содержимого для основного ответа
Вы можете использовать тот же параметр `responses` для добавления различных медиа-типов для одного и того же основного ответа.
Вы можете использовать этот же параметр `responses`, чтобы добавить разные типы содержимого для одного и того же основного ответа.
Например, можно добавить дополнительный медиа-тип `image/png`, объявляя, что ваша *операция пути* может возвращать JSON объект (с медиа-типом `application/json`) или PNG изображение:
Например, вы можете добавить дополнительный медиа-тип `image/png`, объявив, что ваши *операции пути* могут возвращать JSON объект (с медиа-типом `application/json`) или изображение PNG:
Обратите внимание, что вы должны вернуть изображение, используя `FileResponse` напрямую.
Обратите внимание, что вы должны возвращать изображение, используя `FileResponse` напрямую.
///
/// info | Информация
Если вы не укажете явный медиа-тип в вашем `responses` параметре, FastAPI будет предполагать, что ответ имеет тот же медиа-тип, что и основной класс ответа (по умолчанию `application/json`).
Если вы явно не указываете другой тип содержимого в вашем параметре `responses`, FastAPI предположит, что ответ имеет тот же тип содержимого, что и основной класс ответа (по умолчанию `application/json`).
Но если вы указали пользовательский класс ответа с `None` в качестве его медиа-типа, FastAPI будет использовать `application/json` для любого дополнительного ответа, который связан с моделью.
Но если вы задали пользовательский класс ответа с `None` в качестве типа содержимого, FastAPI будет использовать `application/json` для любого дополнительного ответа, у которого есть ассоциированная модель.
///
## Комбинирование информации
Вы также можете объединять информацию об ответах из нескольких мест, включая параметры `response_model`, `status_code` и `responses`.
Вы также можете комбинировать информацию о ответах из разных мест, включая параметры `response_model`, `status_code` и `responses`.
Вы можете объявить `response_model`, используя статус-код по умолчанию`200` (или пользовательский, если вам нужно), а затем объявить дополнительную информацию для этого же ответа в `responses`, напрямую в OpenAPI схеме.
Вы можете объявить `response_model`, используя стандартный статус-код `200` (или пользовательский, если необходимо), а затем объявить дополнительную информацию для этого же ответа в `responses`, напрямую в схеме OpenAPI.
**FastAPI** сохранит дополнительную информацию из `responses` и объединит её с JSON Schema вашей модели.
**FastAPI** сохранит дополнительную информацию из `responses` и объединит ее с JSON Schema из вашей модели.
Например, вы можете объявить ответ с кодом состояния `404`, который использует Pydanticмодель и имеет пользовательское `description`.
Например, вы можете объявить ответ с кодом состояния `404`, который использует Pydantic-модель и имеет пользовательское `description`.
И ответ с кодом состояния `200`, который использует ваш `response_model`, но включает пользовательский `example`:
## Комбинирование предопределенных ответов и пользовательских
## Комбинирование предопределенных и пользовательских ответов
Возможно, вы захотите иметь некоторые предопределенные ответы, которые применяются к многим *операциям пути*, но вы хотите объединить их с пользовательскими ответами, необходимыми для каждой *операции пути*.
Возможно, вы захотите иметь некоторые предопределенные ответы, которые применяются ко многим *операциям пути*, но хотите объединить их с пользовательскими ответами, необходимыми для каждой *операции пути*.
В таких случаях вы можете использовать технику Python "распаковки" `dict` с помощью `**dict_to_unpack`:
Для таких случаев вы можете использовать технику Python "распаковка" `dict` с помощью `**dict_to_unpack`:
```Python
old_dict = {
@ -223,7 +223,7 @@ old_dict = {
new_dict = {**old_dict, "new key": "new value"}
```
Здесь, `new_dict` будет содержать все пары ключ-значение из `old_dict` плюс новую пару ключ-значение:
Здесь, `new_dict` будет содержать все пары `ключ-значение` из `old_dict` плюс новую пару `ключ-значение`:
```Python
{
@ -233,15 +233,15 @@ new_dict = {**old_dict, "new key": "new value"}
}
```
Вы можете использовать эту технику для повторного использования некоторых предопределенных ответов в ваших *операциях пути* и сочетания их с дополнительными пользовательскими ответами.
Вы можете использовать эту технику для повторного использования некоторых предопределенных ответов в ваших *операциях пути* и объединить их с дополнительными пользовательскими.
Чтобы узнать, что именно можно включить в ответы, вы можете проверить эти разделы в спецификации OpenAPI:
Чтобы узнать, что именно можно включить в ответы, вы можете посмотреть эти разделы в спецификации OpenAPI:
* <ahref="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`.
* <ahref="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`.
* <ahref="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`.
В этом случае этот `__call__` используется **FastAPI** для проверки дополнительных параметров и подзависимости, и это то, что будет вызвано для передачи значения в параметр в вашей функции *операции пути* позже.
В этом случае **FastAPI** будет использовать этот `__call__` для проверки дополнительных параметров и под-зависимостей, и он будет вызываться для передачи значения в параметр вашей *функции-обработчика пути* позже.
## Параметризация экземпляра
Теперь мы можем использовать `__init__` для объявления параметров экземпляра, которые мы можем использовать для "параметризации" зависимости:
Теперь мы можем использовать `__init__`, чтобы объявить параметры экземпляра, которые мы можем использовать для "параметризации" зависимости:
Таким образом, мы можем "параметризовать" нашу зависимость, которая теперь содержит`"bar"`, как атрибут `checker.fixed_content`.
Таким образом, мы можем "параметризовать" нашу зависимость, в которой теперь есть`"bar"`, как атрибут `checker.fixed_content`.
## Использование экземпляра в качестве зависимости
Затем мы можем использовать этот `checker` в `Depends(checker)`, вместо `Depends(FixedContentQueryChecker)`, потому что зависимость — это экземпляр `checker`, а не сам класс.
Затем мы могли бы использовать этот `checker` в `Depends(checker)`, вместо `Depends(FixedContentQueryChecker)`, потому что зависимость — это экземпляр `checker`, а не сам класс.
И при разрешении зависимости **FastAPI** вызовет этот `checker`следующим образом:
И при разрешении зависимости,**FastAPI** вызовет этот `checker`так:
```Python
checker(q="somequery")
```
...и передаст то, что это возвращает, как значение зависимости в нашей функции *операции пути* как параметр `fixed_content_included`:
...и передаст то, что это возвращает, как значение зависимости в нашей *функции-обработчике пути* как параметр `fixed_content_included`:
В некоторых случаях вам может понадобиться воспользоваться сервером **прокси**, таким как Traefik или Nginx, с конфигурацией, которая добавляет дополнительный префикс пути, не видимый вашему приложению.
В некоторых ситуациях вам может понадобиться использовать **прокси**-сервер, такой как Traefik или Nginx, с конфигурацией, добавляющей дополнительный префикс к path, который ваше приложение не видит.
В таких случаях вы можете использовать `root_path` для настройки вашего приложения.
`root_path` — это механизм, предусмотренный спецификацией ASGI (на которой построен FastAPI через Starlette).
`root_path` — это механизм, предоставляемый спецификацией ASGI (на основе которой построен FastAPI через Starlette).
`root_path` используется для обработки этих специфических случаев.
`root_path` используется для обработки этих конкретных случаев.
Также он используется внутренне при монтировании подприложений.
Он также используется внутренне при монтировании подприложений.
## Прокси с обрезанным префиксом пути
## Прокси с вырезанным префиксом path
Наличие прокси с обрезанным префиксом пути в этом случае означает, что вы можете объявить путь `/app` в вашем коде, но затем добавить слой сверху (прокси), который поместит ваше приложение **FastAPI** под путь вроде`/api/v1`.
Использование прокси с вырезанным префиксом path означает, что вы можете объявить путь как `/app` в своем коде. Но затем вы добавляете слой сверху (прокси), который помещает ваше приложение **FastAPI** под путь, такой как`/api/v1`.
В этом случае изначальный путь `/app`на самом деле будет обслуживаться по адресу`/api/v1/app`.
В этом случае оригинальный путь `/app`будет фактически работать как`/api/v1/app`.
Хотя весь ваш код написан, предполагая, что существует только `/app`.
Несмотря на то, что весь ваш код написан с учетом использования только `/app`.
И прокси будет **"обрезать"** **префикс пути** на лету перед передачей запроса на сервер приложения (вероятно, через FastAPI CLI через Uvicorn), оставляя ваше приложение уверенным в том, что оно обслуживается по адресу `/app`, чтобы вам не нужно было обновлять весь ваш код для включения префикса `/api/v1`.
И прокси будет **"вырезать"** **префикс пути** на лету перед передачей запроса серверу приложения (вероятно, Uvicorn через CLI FastAPI), оставляя ваше приложение уверенным, что оно работает на `/app`, чтобы вам не пришлось обновлять весь код для включения префикса `/api/v1`.
До этого момента все будет работать как обычно.
Но затем, когда вы откроете интегрированный интерфейс документации (фронтенд), он будет ожидать, что получит схему OpenAPI по адресу`/openapi.json`, вместо `/api/v1/openapi.json`.
Но затем, когда вы откроете встроенный интерфейс документации (фронтенд), он будет ожидать получить OpenAPI-схему на`/openapi.json`, вместо `/api/v1/openapi.json`.
Таким образом, фронтенд (который выполняется в браузере) попытается получить доступ к`/openapi.json` и не сможет получить схему OpenAPI.
Поэтому фронтенд (который работает в браузере) попытается достичь`/openapi.json` и не сможет получить OpenAPI-схему.
Поскольку у нас есть прокси с префиксом пути `/api/v1` для нашего приложения, фронтенд должен получить схему OpenAPI по адресу`/api/v1/openapi.json`.
Поскольку у нас есть прокси с префиксом пути `/api/v1` для нашего приложения, фронтенд должен получить OpenAPI-схему на`/api/v1/openapi.json`.
```mermaid
graph LR
@ -41,13 +41,13 @@ browser --> proxy
proxy --> server
```
/// tip | Совет
/// совет | Совет
IP `0.0.0.0` обычно используется, чтобы указать, что программа слушает все доступные IP-адреса на этой машине/сервере.
///
UI документации также будет нужна схема OpenAPI, чтобы указать, что этот API `server` находится по адресу`/api/v1` (за прокси). Например:
Интерфейс документации также будет нуждаться в OpenAPI-схеме для декларации, что этот API `server` расположен на`/api/v1` (за прокси). Например:
```JSON hl_lines="4-8"
{
@ -64,37 +64,37 @@ UI документации также будет нужна схема OpenAPI,
}
```
В этом примере "Прокси" может быть чем-то вроде **Traefik**. А сервер будет чем-то вроде FastAPI CLI с **Uvicorn**, выполняющим ваше приложение FastAPI.
В этом примере "Прокси" может быть чем-то вроде **Traefik**. А сервером может быть что-то вроде CLI FastAPI с **Uvicorn**, который запускает ваше приложение FastAPI.
### Предоставление `root_path`
### Указание `root_path`
Для этого вы можете использовать параметр командной строки`--root-path`, например:
Чтобы достичь этого, вы можете использовать командную строку с опцией`--root-path`, например:
<divclass="termy">
```console
$ fastapi run main.py --root-path /api/v1
<spanstyle="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
<spanstyle="color: green;">INFO</span>: Uvicorn запущен на http://127.0.0.1:8000 (Нажмите CTRL+C для остановки)
```
</div>
Если вы используете Hypercorn, у него также есть опция `--root-path`.
/// note | Технические детали
/// note | Технические подробности
Спецификация ASGI определяет `root_path` для этого случая использования.
А параметр командной строки `--root-path` предоставляет этот `root_path`.
И опция командной строки `--root-path` предоставляет этот `root_path`.
///
### Проверка текущего `root_path`
Вы можете получить текущий `root_path`, используемый вашим приложением для каждого запроса, он является частью словаря `scope` (который является частью спецификации ASGI).
Вы можете получить текущий `root_path`, используемый вашим приложением для каждого запроса, он является частью словаря `scope` (это часть спецификации ASGI).
Здесь мы приводим его в сообщении только для демонстрационных целей.
Здесь мы включаем его в сообщение просто для демонстрации.
@ -105,60 +105,60 @@ $ fastapi run main.py --root-path /api/v1
```console
$ fastapi run main.py --root-path /api/v1
<spanstyle="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
<spanstyle="color: green;">INFO</span>: Uvicorn запущен на http://127.0.0.1:8000 (Нажмите CTRL+C для остановки)
```
</div>
Ответ будет таким:
Ответ будет что-то вроде:
```JSON
{
"message": "Здравствуй, мир",
"message": "Hello World",
"root_path": "/api/v1"
}
```
### Установка `root_path` в приложении FastAPI
В качестве альтернативы, если у вас нет возможности предоставить параметр командной строки, такой как `--root-path` или эквивалентный, вы можете установить параметр `root_path` при создании вашего приложения FastAPI:
Альтернативно, если у вас нет возможности предоставить опцию командной строки, такую как `--root-path` или аналогичную, вы можете установить параметр `root_path` при создании вашего приложения FastAPI:
Передача `root_path` в `FastAPI` будет эквивалентна передаче параметра командной строки `--root-path` в Uvicorn или Hypercorn.
Передача `root_path` в `FastAPI` будет эквивалентна передаче опции командной строки `--root-path` для Uvicorn или Hypercorn.
### О `root_path`
Учтите, что сервер (Uvicorn) не будет использовать этот `root_path` для чего-либо, кроме передачи его в приложение.
Учтите, что сервер (Uvicorn) не будет использовать `root_path` ни для чего, кроме передачи его приложению.
Но если вы пойдете с вашим браузером на<ahref="http://127.0.0.1:8000"class="external-link"target="_blank">http://127.0.0.1:8000/app</a>, вы увидите нормальный ответ:
Но если вы зайдете через браузер по адресу<ahref="http://127.0.0.1:8000"class="external-link"target="_blank">http://127.0.0.1:8000/app</a>, вы увидите обычный ответ:
```JSON
{
"message": "Здравствуй, мир",
"message": "Hello World",
"root_path": "/api/v1"
}
```
Таким образом, он не будет ожидать доступа по адресу `http://127.0.0.1:8000/api/v1/app`.
Итак, он не будет ожидать доступа по адресу `http://127.0.0.1:8000/api/v1/app`.
Uvicorn будет ожидать, что прокси будет обращаться к Uvicorn по адресу `http://127.0.0.1:8000/app`, и тогда это будет обязанностью прокси добавить дополнительный префикс `/api/v1` поверх.
Uvicorn будет ожидать, что прокси получит доступ к Uvicorn по адресу `http://127.0.0.1:8000/app`, а затем это будет уже ответственность прокси — добавить дополнительный префикс `/api/v1`.
## О прокси с обрезанным префиксом пути
## О прокси с вырезанным префиксом path
Учтите, что использование прокси с обрезанным префиксом пути — это только один из способов его настройки.
Имейте в виду, что прокси с вырезанным префиксом path — это всего лишь один из способов его настройки.
Вероятно, в большинстве случаев по умолчанию прокси не будет иметь обрезанного префикса пути.
Вероятно, во многих случаях по умолчанию будет то, что у прокси нет вырезанного префикса path.
В таком случае (без обрезанного префикса пути) прокси будет слушать что-то вроде `https://myawesomeapp.com`, и тогда, если браузер зайдет на `https://myawesomeapp.com/api/v1/app`, а ваш сервер (например, Uvicorn) слушает на `http://127.0.0.1:8000`, прокси (без обрезанного префикса пути) будет обращаться к Uvicorn по тому же пути: `http://127.0.0.1:8000/api/v1/app`.
В случае, подобном этому (без вырезанного префикса 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
Вы можете легко запустить эксперимент локально с обрезанным префиксом пути, используя <ahref="https://docs.traefik.io/"class="external-link"target="_blank">Traefik</a>.
Вы можете легко провести эксперимент локально с вырезанным префиксом path, используя <ahref="https://docs.traefik.io/"class="external-link"target="_blank">Traefik</a>.
<ahref="https://github.com/containous/traefik/releases"class="external-link"target="_blank">Скачайте Traefik</a>, это единый исполняемый файл, вы можете извлечь сжатый файл и запустить его напрямую из терминала.
<ahref="https://github.com/containous/traefik/releases"class="external-link"target="_blank">Скачайте Traefik</a>, это один исполняемый файл, вы можете извлечь сжатый файл и запустить его прямо из терминала.
Затем создайте файл `traefik.toml` со следующим содержимым:
Затем создайте файл `traefik.toml` с:
```TOML hl_lines="3"
[entryPoints]
@ -172,9 +172,9 @@ Uvicorn будет ожидать, что прокси будет обращат
Это указывает Traefik слушать на порту 9999 и использовать другой файл `routes.toml`.
/// tip | Совет
/// совет
Мы используем порт 9999 вместо стандартного HTTP-порта 80, чтобы вам не нужно было запускать его с административными (`sudo`) привилегиями.
Мы используем порт 9999 вместо стандартного порта HTTP 80, чтобы вам не пришлось запускать это с привилегиями администратора (`sudo`).
///
@ -203,9 +203,9 @@ Uvicorn будет ожидать, что прокси будет обращат
url = "http://127.0.0.1:8000"
```
Этот файл настраивает Traefik на использование префикса пути`/api/v1`.
Этот файл настраивает Traefik для использования префикса path`/api/v1`.
И затем Traefik будет перенаправлять свои запросы на ваш Uvicorn, работающий на `http://127.0.0.1:8000`.
А затем Traefik перенаправит свои запросы на ваш Uvicorn, запущенный на `http://127.0.0.1:8000`.
Теперь запустите Traefik:
@ -214,98 +214,98 @@ Uvicorn будет ожидать, что прокси будет обращат
```console
$ ./traefik --configFile=traefik.toml
INFO[0000] Configuration loaded from file: /home/user/awesomeapi/traefik.toml
INFO[0000] Конфигурация загружена из файла: /home/user/awesomeapi/traefik.toml
```
</div>
И теперь запустите ваше приложение, используя параметр`--root-path`:
И теперь запустите ваше приложение, используя опцию`--root-path`:
<divclass="termy">
```console
$ fastapi run main.py --root-path /api/v1
<spanstyle="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
<spanstyle="color: green;">INFO</span>: Uvicorn запущен на http://127.0.0.1:8000 (Нажмите CTRL+C для остановки)
```
</div>
### Проверка ответов
### Проверьте ответы
Теперь, если вы перейдете по URL-адресу с портом для Uvicorn: <ahref="http://127.0.0.1:8000/app"class="external-link"target="_blank">http://127.0.0.1:8000/app</a>, вы увидите стандартный ответ:
Теперь, если вы перейдете по URL с портом для Uvicorn: <ahref="http://127.0.0.1:8000/app"class="external-link"target="_blank">http://127.0.0.1:8000/app</a>, вы увидите нормальный ответ:
```JSON
{
"message": "Здравствуй, мир",
"message": "Hello World",
"root_path": "/api/v1"
}
```
/// tip | Совет
/// совет | Совет
Заметьте, что, хотя вы обращаетесь к нему по адресу `http://127.0.0.1:8000/app`, он показывает `root_path` как `/api/v1`, взятый из параметра`--root-path`.
Обратите внимание, что, хотя вы получаете доступ к нему по адресу `http://127.0.0.1:8000/app`, он отображает `root_path` как `/api/v1`, взятый из опции`--root-path`.
///
А теперь откройте URL с портом для Traefik, включая префикс пути: <ahref="http://127.0.0.1:9999/api/v1/app"class="external-link"target="_blank">http://127.0.0.1:9999/api/v1/app</a>.
А теперь откройте URL с портом для Traefik, включая префикс path: <ahref="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": "Здравствуй, мир",
"message": "Hello World",
"root_path": "/api/v1"
}
```
только на этот раз по адресу URL с префиксом, предоставленным прокси: `/api/v1`.
но на этот раз по URL с префиксом path, предоставленным прокси: `/api/v1`.
Конечно, идея в том, чтобы все получали доступ к приложению через прокси, поэтому версия с префиксом пути `/api/v1` является "правильной".
Конечно, идея здесь в том, что все должны получить доступ к приложению через прокси, так что версия с префиксом path `/api/v1` — это "правильная" версия.
А версия без префикса пути (`http://127.0.0.1:8000/app`), предоставленная напрямую Uvicorn, будет исключительно для доступа _прокси_ (Traefik) к нему.
И версия без префикса path (`http://127.0.0.1:8000/app`), предоставляемая напрямую Uvicorn, будет исключительно для _прокси_ (Traefik), чтобы получить к ней доступ.
Это демонстрирует, как прокси (Traefik) использует префикс пути и как сервер (Uvicorn) использует `root_path` из параметра`--root-path`.
Это демонстрирует, как прокси (Traefik) использует префикс path и как сервер (Uvicorn) использует `root_path` из опции`--root-path`.
### Проверка UI документации
### Проверьте интерфейс документации
Но вот интересная часть. ✨
Но вот где начинается веселье. ✨
"Официальный" способ доступа к приложению будет через прокси с префиксом пути, который мы определили. Таким образом, как мы и ожидали, если вы попытаетесь использовать UI документации, предоставляемый напрямую Uvicorn, без префикса пути в URL, это не будет работать, так как он ожидает, что доступ будет через прокси.
"Официальный" способ доступа к приложению будет через прокси с префиксом path, который мы определили. Поэтому, как и ожидалось, если вы попытаетесь использовать интерфейс документации, обслуживаемый Uvicorn напрямую, без префикса path в URL, он не будет работать, потому что предполагается, что доступ будет осуществляться через прокси.
Вы можете проверить это на<ahref="http://127.0.0.1:8000/docs"class="external-link"target="_blank">http://127.0.0.1:8000/docs</a>:
Вы можете проверить это по адресу<ahref="http://127.0.0.1:8000/docs"class="external-link"target="_blank">http://127.0.0.1:8000/docs</a>:
Но если мы получим доступ к UI документации по "официальному" URL с использованием прокси с портом `9999`, по `/api/v1/docs`, он работает правильно! 🎉
Но если мы получим доступ к интерфейсу документации по "официальному" URL, используя прокси с портом `9999`, по адресу`/api/v1/docs`, он работает правильно! 🎉
Можно проверить это на<ahref="http://127.0.0.1:9999/api/v1/docs"class="external-link"target="_blank">http://127.0.0.1:9999/api/v1/docs</a>:
Вы можете проверить это по адресу<ahref="http://127.0.0.1:9999/api/v1/docs"class="external-link"target="_blank">http://127.0.0.1:9999/api/v1/docs</a>:
Это потому, что FastAPI использует этот `root_path` для создания стандартного `server` в OpenAPI с URL, предоставленным `root_path`.
Это потому, что FastAPI использует этот `root_path` для создания `server` по умолчанию в OpenAPI с URL, предоставленным `root_path`.
## Дополнительные серверы
/// warning | Предупреждение
Это более сложный случай использования. Смело пропускайте его.
Это более сложный случай использования. Вы можете пропустить его, если хотите.
///
По умолчанию **FastAPI** создаст `server` в схеме OpenAPI с URL для `root_path`.
Но вы также можете предоставить другие альтернативные `servers`, например, если вы хотите, чтобы *тот же* UI документации взаимодействовал как с окружением для тестирования, так и с производственным окружением.
Но вы также можете предоставить другие альтернативные `servers`, например, если вы хотите, чтобы *этот же* интерфейс документации взаимодействовал как со средой тестирования, так и со средой продакшн.
Если вы передадите кастомный список `servers` и присутствует `root_path` (потому что ваш API находится за прокси), **FastAPI** вставит "server" с этим `root_path` в начало списка.
Если вы передаете пользовательский список `servers` и существует `root_path` (потому что ваш API находится за прокси), **FastAPI** вставит "server" с этим `root_path` в начало списка.
@ -317,11 +317,11 @@ $ fastapi run main.py --root-path /api/v1
},
{
"url": "https://stag.example.com",
"description": "Staging environment"
"description": "Тестовая среда"
},
{
"url": "https://prod.example.com",
"description": "Production environment"
"description": "Продакшн среда"
}
],
"paths": {
@ -330,23 +330,23 @@ $ fastapi run main.py --root-path /api/v1
}
```
/// tip | Совет
/// совет | Совет
Обратите внимание на автоматически сгенерированный сервер с значением `url``/api/v1`, взятое из `root_path`.
Обратите внимание на автоматически сгенерированный сервер с `url` значением `/api/v1`, взятым из `root_path`.
///
В UI документации по адресу <ahref="http://127.0.0.1:9999/api/v1/docs"class="external-link"target="_blank">http://127.0.0.1:9999/api/v1/docs</a> это будет выглядеть так:
В интерфейсе документации по адресу <ahref="http://127.0.0.1:9999/api/v1/docs"class="external-link"target="_blank">http://127.0.0.1:9999/api/v1/docs</a> это будет выглядеть так:
Если вы не хотите, чтобы **FastAPI** включал автоматический сервер, используя `root_path`, вы можете использовать параметр `root_path_in_servers=False`:
@ -356,6 +356,6 @@ UI документации будет взаимодействовать с с
## Монтирование подприложения
Если вам нужно смонтировать подприложение (как описано в [Подприложения - Монтирование](sub-applications.md){.internal-link target=_blank}), при этом используя прокси с `root_path`, вы можете сделать это нормально, как вы и ожидаете.
Если вам нужно смонтировать подприложение (как описано в [Подприложения - Монтирование](sub-applications.md){.internal-link target=_blank}), используя также прокси с `root_path`, вы можете сделать это обычным образом, как и ожидалось.
FastAPI будет внутри использовать `root_path` умно, так что все будет просто работать. ✨
FastAPI будет использовать `root_path` умно, поэтому все будет работать, как и задумано. ✨
# Пользовательский ответ - HTML, поток, файл и другие
По умолчанию,**FastAPI** будет возвращать ответы, используя `JSONResponse`.
По умолчанию **FastAPI** будет возвращать ответы, используя `JSONResponse`.
Вы можете переопределить это, возвращая `Response` напрямую, как показано в [Возврат ответа напрямую](response-directly.md){.internal-link target=_blank}.
Вы можете переопределить это, возвращая `Response` напрямую, как показано в разделе [Возврат Response напрямую](response-directly.md){.internal-link target=_blank}.
Но если вы возвращаете `Response` напрямую (или любой подкласс, например, `JSONResponse`), данные не будут автоматически преобразованы (даже если вы объявите `response_model`), и документация не будет автоматически сгенерирована (например, включая конкретный "media type", в HTTP заголовке `Content-Type` как часть сгенерированного OpenAPI).
Но если вы возвращаете `Response` напрямую (или любой подкласс, такой как `JSONResponse`), данные не будут автоматически конвертироваться (даже если вы объявите `response_model`), и документация не будет автоматически генерироваться (например, включая конкретный "тип содержимого", в HTTP-заголовке `Content-Type` как часть генерируемого OpenAPI).
Но вы также можете объявить `Response`, который вы хотите использовать (например, любой подкласс `Response`), в *декораторе операции пути* с использованием параметра`response_class`.
Но вы также можете объявить `Response`, который хотите использовать (например, любой подкласс `Response`), в *декораторе операции пути*, используя параметр`response_class`.
Содержание, которое вы возвращаете из вашей *функции операции пути*, будет помещено внутрь этого `Response`.
Содержимое, которое вы возвращаете из вашей *функции-обработчика пути*, будет помещено внутрь этого `Response`.
И если этот `Response` имеет тип носителя JSON (`application/json`), как в случае с `JSONResponse` и `UJSONResponse`, данные, которые вы возвращаете, будут автоматически преобразованы (и отфильтрованы) с любым Pydantic `response_model`, который вы объявили в *декораторе операции пути*.
И если этот `Response` имеет JSON тип содержимого (`application/json`), как в случае с `JSONResponse` и `UJSONResponse`, данные, которые вы возвращаете, будут автоматически конвертироваться (и фильтроваться) с помощью любого Pydantic `response_model`, который вы объявили в *декораторе операции пути*.
/// note | Заметка
/// note | Замечание
Если вы используете класс ответа без типа носителя, FastAPI ожидает, что ваш ответ будет без содержимого, поэтому формат ответа не будет задокументирован в сгенерированных OpenAPI документах.
Если вы используете класс ответа без типа содержимого, FastAPI ожидает, что ваш ответ не будет содержать контент, поэтому он не задокументирует формат ответа в автоматически генерируемой документации OpenAPI.
///
## Используйте `ORJSONResponse`
## Использование `ORJSONResponse`
Например, если вы хотите повысить производительность, вы можете установить и использовать <ahref="https://github.com/ijl/orjson"class="external-link"target="_blank">`orjson`</a> и установить ответ в`ORJSONResponse`.
Например, если вы заботитесь о производительности, вы можете установить и использовать <ahref="https://github.com/ijl/orjson"class="external-link"target="_blank">`orjson`</a> и установить ответ `ORJSONResponse`.
Импортируйте класс `Response` (подкласс), который вы хотите использовать, и объявите его в *декораторе операции пути*.
Импортируйте класс (подкласс) `Response`, который вы хотите использовать, и объявите его в *декораторе операции пути*.
Для больших ответов возврат `Response` напрямую значительно быстрее, чем возврат словаря.
Это потому, что по умолчанию, FastAPI будет проверять каждый элемент внутри и удостоверяться, что он сериализуем в JSON, используя тот же [Совместимый с JSON кодировщик](../tutorial/encoder.md){.internal-link target=_blank}, описанный в тутореале. Это то, что позволяет вам возвращать **произвольные объекты**, например, модели базы данных.
Это происходит потому, что по умолчанию FastAPI будет проверять каждый элемент внутри и убеждаться, что он сериализуем как JSON, используя тот же [Совместимый с JSON кодировщик](../tutorial/encoder.md){.internal-link target=_blank}, о котором рассказано в руководстве. Это позволяет вам возвращать **произвольные объекты**, например, модели базы данных.
Но если вы уверены, что содержание, которое вы возвращаете, **сериализуемо в JSON**, вы можете передать его напрямую в класс ответов и избежать дополнительной нагрузки, которая возникла бы у FastAPI, пропуская ваше возвращаемое содержимое через `jsonable_encoder` перед передачей его в класс ответов.
Но если вы уверены, что содержимое, которое вы возвращаете, **сериализуемо с JSON**, вы можете передать его напрямую в класс ответа и избежать дополнительной нагрузки, которую FastAPI имел бы, передавая возвращаемый контент через `jsonable_encoder` перед передачей его в класс ответа.
Параметр `response_class` также будет использоваться для определения "media type" ответа.
Параметр `response_class` также будет использоваться для определения "типа содержимого" ответа.
В этом случае, HTTP заголовок `Content-Type` будет установлен в `text/html`.
В этом случае HTTP-заголовок `Content-Type` будет установлен в `text/html`.
И он будет задокументирован как таковой в OpenAPI.
@ -69,41 +69,41 @@
### Возврат `Response`
Как показано в [Возврат ответа напрямую](response-directly.md){.internal-link target=_blank}, вы также можете переопределить ответ напрямую в вашей *операции пути*, возвращая его.
Как показано в разделе [Возврат Response напрямую](response-directly.md){.internal-link target=_blank}, вы также можете переопределить ответ напрямую в вашей *операции пути*, возвращая его.
Тот же самый пример, возвращающий `HTMLResponse`, мог бы выглядеть следующим образом:
Тот же пример, что и выше, возвращающий `HTMLResponse`, может выглядеть так:
`Response`, возвращенный напрямую вашей *функцией операции пути*, не будет задокументирован в OpenAPI (например, `Content-Type` не будет документирован) и не будет виден в автоматической интерактивной документации.
`Response`, возвращенный напрямую вашей *функцией-обработчиком пути*, не будет задокументирован в OpenAPI (например, `Content-Type` не будет задокументирован) и не будет виден в автоматически генерируемой интерактивной документации.
///
/// info | Информация
Конечно, фактический заголовок `Content-Type`, код статуса и т.д. будут исходить из объекта `Response`, который вы вернули.
Конечно, фактический заголовок `Content-Type`, статус-код и др., будут из объекта `Response`, который вы вернули.
///
### Документирование в OpenAPI и переопределение `Response`
Если вы хотите переопределить ответ внутри функции, но в то же время задокументировать "media type" в OpenAPI, вы можете использовать параметр `response_class` И вернуть объект `Response`.
Если вы хотите переопределить ответ из функции, но при этом документация должна содержать "тип содержимого" в OpenAPI, вы можете использовать параметр `response_class` И вернуть объект `Response`.
`response_class` будет использоваться только для документирования *операции пути* в OpenAPI, но ваш `Response` будет использоваться как есть.
Тогда `response_class` будет использоваться только для документирования OpenAPI *операции пути*, но ваш `Response` будет использоваться как есть.
В этом примере функция `generate_html_response()` уже генерирует и возвращает `Response` вместо возврата HTML в `str`.
В этом примере функция `generate_html_response()` уже генерирует и возвращает `Response` вместо возврата HTML в виде `str`.
Возвращая результат вызова `generate_html_response()`, вы уже возвращаете `Response`, который переопределяет стандартное поведение **FastAPI**.
Возвращая результат вызова `generate_html_response()`, вы уже возвращаете `Response`, который переопределит поведение **FastAPI** по умолчанию.
Но, поскольку вы также передали `HTMLResponse` в `response_class`, **FastAPI** будет знать, как задокументировать это в OpenAPI и интерактивной документации, как HTML с `text/html`:
Но так как вы также передали `HTMLResponse` в `response_class`, **FastAPI** будет знать, как задокументировать это в OpenAPI и интерактивной документации как HTML с `text/html`:
Имейте в виду, что вы можете использовать `Response`, чтобы вернуть что-либо еще, или даже создать собственный подкласс.
Имейте в виду, что вы можете использовать `Response` для возврата чего-либо еще или даже создать собственный подкласс.
/// note | Технические подробности
/// note | Технические детали
Вы также могли бы использовать `from starlette.responses import HTMLResponse`.
Вы также можете использовать `from starlette.responses import HTMLResponse`.
**FastAPI** предоставляет тот же `starlette.responses` как `fastapi.responses` просто для вашего удобства как разработчика. Но большинство доступных ответов поступают непосредственно из Starlette.
**FastAPI** предоставляет те же `starlette.responses`, что и `fastapi.responses`, просто для вашего удобства, как разработчика. Но большинство доступных ответов поступают напрямую из Starlette.
///
### `Response`
Основной класс `Response`, все другие ответы наследуются от него.
Основной класс `Response`, все остальные ответы наследуются от него.
Вы можете вернуть его напрямую.
Он принимает следующие параметры:
* `content` - `str` или `bytes`.
* `status_code` - `int` HTTP код состояния.
* `status_code` - `int` HTTP статус-код.
* `headers` - `dict` строк.
* `media_type` - `str`, указывающая тип носителя. Например,`"text/html"`.
* `media_type` - `str`, который задает тип содержимого, например`"text/html"`.
FastAPI (фактически Starlette) автоматически включит заголовок Content-Length. Также будет включен заголовок Content-Type, основанный на `media_type` и добавлением кодировки для текстовых типов.
FastAPI (фактически Starlette) автоматически добавит заголовок Content-Length. Он также включит заголовок Content-Type, основанный на `media_type` и добавит кодировку для текстовых типов.
Принимает некоторые данные и возвращает закодированный ответ`application/json`.
Принимает данные и возвращает ответ, кодированный как`application/json`.
Это стандартный ответ, используемый в **FastAPI**, как вы прочли выше.
Это ответ по умолчанию, используемый в **FastAPI**, как вы прочли выше.
### `ORJSONResponse`
Быстрая альтернатива JSON ответу, используя <ahref="https://github.com/ijl/orjson"class="external-link"target="_blank">`orjson`</a>, как вы прочли выше.
Быстрая альтернатива JSON-ответа, использующая <ahref="https://github.com/ijl/orjson"class="external-link"target="_blank">`orjson`</a>, как вы прочли выше.
/// info | Информация
Это требует установки`orjson`, например с `pip install orjson`.
Для этого требуется установка`orjson`, например с помощью`pip install orjson`.
#### Использование `StreamingResponse` с объектами подобными файлам
#### Использование `StreamingResponse` с объектами, похожими на файлы
Если у вас есть объект подобный файлу (например, объект, возвращаемый `open()`), вы можете создать функцию-генератор для итерации по этому объекту подобному файлу.
Если у вас есть объект, похожий на файл (например, объект, возвращаемый `open()`), вы можете создать функцию-генератор для итерации по этому объекту.
Таким образом, вам не придется предварительно полностью загружать его в память, и вы можете передать эту функцию-генератор в `StreamingResponse` и вернуть его.
Таким образом, вам не нужно предварительно загружать все в память, и вы можете передать эту функцию-генератор в `StreamingResponse` и вернуть ее.
Это включает многие библиотеки для работы с облачным хранилищем, обработки видео и другие.
Это включает множество библиотек для взаимодействия с облачным хранилищем, обработки видео и других.
1. Это функция-генератор. Это "функция-генератор", потому что внутри содержит `yield` операторы.
2. Используя `with` блок, мы удостоверяемся, что объект подобный файлу закрыт после завершения работы функции-генератора. То есть, после завершения отправки ответа.
3. Этот `yield from` указывает функции итерироваться по объекту `file_like`. А затем, для каждой части итерации, выдавать эту часть как идущую из этой функции-генератора (`iterfile`).
1. Это функция-генератор. Это "функция-генератор", потому что она содержит оператор `yield`.
2. Используя блок `with`, мы обеспечиваем закрытие объекта, похожего на файл, после завершения выполнения функции-генератора. То есть после того, как отправка ответа завершена.
3. Этот `yield from` указывает функции итерироваться по объекту `file_like`. Затем для каждой части, которую итерация проходит, возвращать эту часть из функции-генератора (`iterfile`).
Таким образом, это функция-генератор, которая передает работу по "генерации" чему-то еще внутри себя.
Таким образом, это функция-генератор, которая передает работу по "генерации" чему-то еще во внутренности.
Делая это таким образом, мы можем поместить это в `with` блок и, таким образом, удостовериться, что объект подобный файлу закрыт после завершения.
Делая так, мы можем поместить это в блок `with` и таким образом обеспечить закрытие объекта, похожего на файл, после завершения.
/// tip | Совет
Обратите внимание, что здесь, так как мы используем стандартный `open()`, который не поддерживает `async` и `await`, мы объявляем операцию пути с помощью обычной`def`.
Обратите внимание, что здесь, так как мы используем стандартный `open()`, который не поддерживает `async` и `await`, мы объявляем операцию пути с использованием обычного`def`.
///
### `FileResponse`
Асинхронно потоком передает файл в качестве ответа.
Асинхронно отправляет файл в поток как ответ.
Принимает другой набор аргументов для инстанцирования по сравнению с другими типами ответов:
Принимает другой набор аргументов для создания экземпляра, чем другие типы ответов:
* `path` - Путь к файлу для потоковой передачи.
* `headers` - Любые пользовательские заголовки для включения, как словарь.
* `media_type` - Строка, указывающая тип носителя. Если не указан, имя файла или путь будут использованы для определения типа носителя.
* `filename` - Если установлен, это будет включено в ответ `Content-Disposition`.
* `path` - Путь к файлу, который необходимо отправить в поток.
* `headers` - Любые пользовательские заголовки, которые необходимо включить, в виде словаря.
* `media_type` - Строка, задающая тип содержимого. Если не установлено, для определения типа содержимого будет использоваться имя файла или путь.
* `filename` - Если установлено, это будет включено в заголовок `Content-Disposition` ответа.
Ответы с файлами будут включать соответствующие заголовки `Content-Length`, `Last-Modified` и `ETag`.
Ответы на файлы будут включать соответствующие заголовки `Content-Length`, `Last-Modified` и `ETag`.
В этом случае вы можете вернуть путь к файлу напрямую из вашей *функции операции пути*.
В этом случае, вы можете вернуть путь к файлу напрямую из вашей *функции-обработчика пути*.
## Пользовательский класс ответа
## Класс пользовательского ответа
Вы можете создать собственный пользовательский класс ответа, наследуясь от `Response` и используя его.
Вы можете создать свой собственный класс пользовательского ответа, наследуя от `Response` и используя его.
Например, давайте скажем, что вы хотите использовать <ahref="https://github.com/ijl/orjson"class="external-link"target="_blank">`orjson`</a>, но с некоторыми пользовательскими настройками, которые не используются в включенном классе `ORJSONResponse`.
Например, предположим, вы хотите использовать <ahref="https://github.com/ijl/orjson"class="external-link"target="_blank">`orjson`</a>, но с некоторыми пользовательскими настройками, не используемыми в включенном классе `ORJSONResponse`.
Давайте скажем, вы хотите, чтобы он возвращал отформатированный и с отступами JSON, поэтому вы хотите использовать опцию orjson `orjson.OPT_INDENT_2`.
Допустим, вы хотите, чтобы он возвращал JSON с отступами и форматированием, поэтому вы хотите использовать опцию orjson `orjson.OPT_INDENT_2`.
Вы могли бы создать `CustomORJSONResponse`. Основное, что вам нужно сделать, это создать метод `Response.render(content)`, который возвращает содержимое в виде`bytes`:
Вы могли бы создать `CustomORJSONResponse`. Главное, что вам нужно сделать, это создать метод `Response.render(content)`, который возвращает содержимое как`bytes`:
Вы все равно можете переопределить `response_class` в *операциях пути* как прежде.
Вы все еще можете переопределять `response_class` в *операциях пути*, как и раньше.
///
## Дополнительная документация
Вы также можете объявить тип носителя и многие другие детали в OpenAPI, используя `responses`: [Дополнительные ответы в OpenAPI](additional-responses.md){.internal-link target=_blank}.
Вы также можете объявить тип содержимого и многие другие детали в OpenAPI, используя `responses`: [Дополнительные ответы в OpenAPI](additional-responses.md){.internal-link target=_blank}.
FastAPI построен на **Pydantic**, и я показывал вам, как использовать модели Pydantic для объявления запросов и ответов.
FastAPI построен на основе **Pydantic**, и я уже показывал вам, как использовать модели Pydantic для объявления HTTP-запросов и HTTP-ответов.
Но FastAPI также поддерживает использование <ahref="https://docs.python.org/3/library/dataclasses.html"class="external-link"target="_blank">`dataclasses`</a> таким же образом:
Это все еще поддерживается благодаря **Pydantic**, так как он имеет<ahref="https://docs.pydantic.dev/latest/concepts/dataclasses/#use-of-stdlib-dataclasses-with-basemodel"class="external-link"target="_blank">внутреннюю поддержку`dataclasses`</a>.
Это по-прежнему поддерживается благодаря **Pydantic**, так как в нем есть<ahref="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, FastAPI использует Pydantic для преобразования стандартных dataclasses в собственный вариант dataclasses от Pydantic.
И, конечно, это поддерживает то же самое:
И, конечно же, он поддерживает то же самое:
* валидацию данных
* сериализацию данных
* документацию данных и т. д.
* валидация данных
* сериализация данных
* документация данных и т.д.
Это работает так же, как и с моделями Pydantic. И фактически это достигается тем же способом, внутрь посредством Pydantic.
Это работает так же, как и с моделями Pydantic. И это фактически достигается таким же образом, под капотом, с использованием Pydantic.
/// info | Информация
Имейте в виду, что dataclasses не могут сделать все, что могут модели Pydantic.
Имейте в виду, что dataclasses не могут выполнять все то, что могут делать модели Pydantic.
Поэтому вам все равно может понадобиться использовать модели Pydantic.
Поэтому, возможно, вам все равно придется использовать модели Pydantic.
Но если у вас есть несколько `dataclasses`, это хороший трюк, чтобы использовать их для питания веб-API с помощью FastAPI. 🤓
Но если у вас есть много dataclasses, это отличный трюк, чтобы использовать их для создания веб-API с помощью FastAPI. 🤓
///
## Dataclasses в `response_model`
Вы можете также использовать `dataclasses` в параметре `response_model`:
Вы также можете использовать `dataclasses` в параметре `response_model`:
Dataclass будет автоматически преобразован в dataclass Pydantic.
Dataclass будет автоматически преобразован в Pydantic dataclass.
Таким образом, его схема будет отображаться в пользовательском интерфейсе API документации:
Таким образом, его схема будет отображаться в интерфейсе документации API:
<imgsrc="/img/tutorial/dataclasses/image01.png">
## Dataclasses в вложенных структурах данных
## Dataclasses в Вложенных Структурах Данных
Вы также можете комбинировать `dataclasses` с другими аннотациями типов, чтобы создавать вложенные структуры данных.
Вы также можете комбинировать `dataclasses` с другими аннотациями типов для создания вложенных структур данных.
В некоторых случаях вам все же может понадобиться использовать версию `dataclasses` от Pydantic. Например, если у вас возникают ошибки с автоматически сгенерированной документацией API.
В некоторых случаях, возможно, вам все равно придется использовать версию `dataclasses` от Pydantic. Например, если у вас возникли ошибки с автоматической генерацией документации API.
В этом случае вы можете просто заменить стандартные `dataclasses` на `pydantic.dataclasses`, что является полноценной заменой:
В этом случае вы можете просто заменить стандартные `dataclasses` на `pydantic.dataclasses`, что является заменой "на лету":
1. Мы по-прежнему импортируем `field` из стандартных `dataclasses`.
2. `pydantic.dataclasses` является полноценной заменой для `dataclasses`.
2. `pydantic.dataclasses` является заменой "на лету" для `dataclasses`.
3. Dataclass `Author` включает в себя список `Item` dataclasses.
3. Dataclass `Author` включает список dataclasses`Item`.
4. Dataclass `Author` используется как параметр `response_model`.
5. Вы можете использовать другие стандартные аннотации типов с dataclasses в качестве тела запроса.
5. Вы можете использовать другие стандартные аннотации типов с dataclasses как HTTP-тело запроса.
В этом случае это список `Item` dataclasses.
В этом случае это список dataclasses `Item`.
6. Здесь мы возвращаем словарь, содержащий `items`, который представляет собой список dataclasses.
6. Здесь мы возвращаем словарь, содержащий `items`, который является списком dataclasses.
FastAPI все еще способен <abbrtitle="преобразовать данные в формат, который может быть передан">сериализовать</abbr> данные в JSON.
FastAPI по-прежнему способен <abbrtitle="конвертирование данных в формат, который может быть передан">сериализовать</abbr> данные в JSON.
7. Здесь `response_model` использует аннотацию типа списка `Author` dataclasses.
7. Здесь `response_model` использует аннотацию типа список dataclasses `Author`.
Опять же, вы можете сочетать `dataclasses` с стандартными аннотациями типов.
Снова вы можете комбинировать `dataclasses` с стандартными аннотациями типов.
8. Обратите внимание, что эта *функция операции пути* использует обычный `def` вместо `async def`.
8. Обратите внимание, что эта *функция-обработчик пути* использует обычный `def` вместо `async def`.
Как всегда, в FastAPI вы можете комбинировать `def` и `async def` по мере необходимости.
Как всегда, в FastAPI вы можете сочетать `def` и `async def` в зависимости от необходимости.
Если вам нужно повторить, когда использовать что, взгляните на раздел _"В спешке?"_ в документации о [`async` и `await`](../async.md#in-a-hurry){.internal-link target=_blank}.
Если вы забыли, когда использовать какой, посмотрите раздел _"Нет времени?"_ в документации о [`async` и `await`](../async.md#in-a-hurry){.internal-link target=_blank}.
9. Эта *функция операции пути* не возвращает dataclasses (хотя могла бы), а список словарей с внутренними данными.
9. Эта *функция-обработчик пути* не возвращает dataclasses (хотя могла бы), а список словарей с внутренними данными.
FastAPI использует параметр `response_model` (который включает dataclasses) для преобразования ответа.
FastAPI будет использовать параметр `response_model` (который включает dataclasses), чтобы преобразовать ответ.
Вы можете комбинировать `dataclasses` с другими аннотациями типов во множестве различных комбинаций для формирования сложных структур данных.
Вы можете комбинировать `dataclasses` с другими аннотациями типов в различных комбинациях, чтобы формировать сложные структуры данных.
Проверьте советы по аннотациям в коде выше, чтобы увидеть более конкретные детали.
Посмотрите советы по аннотации в коде выше, чтобы узнать больше деталей.
## Узнать больше
Вы также можете комбинировать `dataclasses` с другими моделями Pydantic, наследовать от них, включать их в свои собственные модели и т.д.
Вы также можете комбинировать `dataclasses` с другими моделями Pydantic, наследовать их, включать их в свои собственные модели и т.д.
Чтобы узнать больше, ознакомьтесь с <ahref="https://docs.pydantic.dev/latest/concepts/dataclasses/"class="external-link"target="_blank">документацией Pydantic о dataclasses</a>.
Вы можете определить логику (код), которая должна выполняться перед тем, как приложение **запустится**. Это означает, что этот код будет выполнен **один раз**, **до** того как приложение **начнет принимать запросы**.
Вы можете определить логику (код), которая должна быть выполнена перед запуском приложения. Это значит, что данный код будет выполнен **единожды**, **перед** тем, как приложение **начнет принимать HTTP-запросы**.
Таким же образом, вы можете определить логику (код), которая должна выполняться при **завершении работы** приложения. В этом случае этот код будет выполнен **один раз**, **после** обработки возможных **многочисленных запросов**.
Аналогично, вы можете определить логику (код), которая должна быть выполнена при остановке приложения. В этом случае код будет выполнен **единожды**, **после** обработки, возможно, **множества HTTP-запросов**.
Так как этот код выполняется до того, как приложение **начнет** принимать запросы, и сразу после того как оно **закончит** их обрабатывать, он покрывает весь **жизненный цикл** приложения (слово "жизненный цикл" будет важно через секунду 😉).
Поскольку этот код выполняется до того, как приложение **начинает** принимать запросы, и непосредственно после того, как оно **заканчивает** обработку запросов, он охватывает весь **lifespan** приложения (слово "lifespan" сейчас станет важным 😉).
Это может быть очень полезно для настройки **ресурсов**, которые вам нужно использовать для всего приложения, и которые **разделяются** между запросами, и/или которые нужно **очистить** впоследствии. Например, пул соединений с базой данных или загрузка общего алгоритма машинного обучения.
Это может быть очень полезно для настройки **ресурсов**, которые вам нужно использовать для всего приложения, и которые **разделяются** между запросами, и/или которые требуют **освобождения** после завершения работы. Например, пул подключений к базе данных или загрузка общей модели машинного обучения.
## Пример использования
Начнем с примера **использования**, а затем посмотрим, как это решить.
Давайте начнем с примера **использования**, а затем посмотрим, как его решить.
Представьте, что у вас есть несколько **моделей машинного обучения**, которые вы хотите использовать для обработки запросов. 🤖
Предположим, у вас есть несколько **моделей машинного обучения**, которые вы хотите использовать для обработки запросов. 🤖
Эти же модели разделяются между запросами, так что это не одна модель на запрос или одна на пользователя или что-то подобное.
Те же самые модели разделяются между всеми запросами, то есть это не одна модель на запрос или одна на пользователя или что-то подобное.
Представьте, что загрузка модели может **занимать довольно много времени**, потому что ей нужно прочесть много **данных с диска**. Поэтому вы не хотите делать это для каждого запроса.
Представим, что загрузка модели может **занять значительное время**, потому что необходимо прочитать много **данных с диска**. Поэтому вы не хотите делать это для каждого запроса.
Вы могли бы загрузить её на верхнем уровне модуля/файла, но это также означало бы, что модель будет **загружена**, даже если вы просто запускаете простой автоматический тест, и тогда этот тест будет **медленным**, потому что ему придется ждать загрузки модели перед выполнением независимой части кода.
Вы можете загрузить ее на верхнем уровне модуля/файла, но это также означало бы, что модель **загрузится**, даже если вы просто запускаете простой автоматизированный тест, и такой тест был бы **медленный**, так как пришлось бы ждать, пока модель загрузится, прежде чем можно было бы выполнить независимую часть кода.
Вот это мы и решим: загрузим модель перед обработкой запросов, но только прямо перед тем, как приложение начнёт принимать запросы, а не во время загрузки кода.
Это то, что мы решаем: давайте загрузим модель до того, как запросы начнут обрабатываться, но только непосредственно перед тем, как приложение начнет принимать запросы, а не в процессе загрузки кода.
## Жизненный цикл
## Lifespan
Вы можете определить логику для*запуска* и *завершения работы* с использованием параметра `lifespan` приложения `FastAPI` и "контекстного менеджера" (я покажу вам, что это такое, через секунду).
Вы можете определить эту логику *запуска* и *выключения* с помощью параметра `lifespan` в приложении `FastAPI` и "менеджера контекста" (я покажу вам, что это такое, через мгновение).
Начнем с примера, а затем рассмотрим его подробно.
Давайте начнем с примера и затем разберем его подробно.
Мы создаем асинхронную функцию `lifespan()` с использованием `yield`, как показано ниже:
Создаем асинхронную функцию `lifespan()` с `yield` следующим образом:
Здесь мы симулируем дорогую *операцию старта* загрузки модели, помещая (фейковую) функцию модели в словарь с моделями машинного обучения до`yield`. Этот код будет выполнен **до** того, как приложение **начнет принимать запросы**, в момент *старта*.
Здесь мы симулируем дорогостоящую операцию *запуска*, загружая модель, помещая (фальшивую) функцию модели в словарь с моделями машинного обучения перед `yield`. Этот код будет выполнен **до** того, как приложение **начнет принимать запросы**, в процессе *запуска*.
А затем, сразу после `yield`, мы выгружаем модель. Этот код будет выполнен **после** того, как приложение **закончит обрабатывать запросы**, прямо перед *завершением работы*. Это может, например, освободить такие ресурсы, как память или GPU.
И затем, сразу после `yield`, мы выгружаем модель. Этот код будет выполнен **после** того, как приложение **завершит обработку запросов**, непосредственно перед *выключением*. Это может, например, освободить такие ресурсы, как память или GPU.
/// tip | Совет
Завершение работы происходит, когда вы **останавливаете** приложение.
Выключение происходит, когда вы **останавливаете** приложение.
Возможно, вам нужно запустить новую версию или вы просто устали его запускать. 🤷
Возможно, вам нужно запустить новую версию, или вы просто устали от его работы. 🤷
///
### Функция жизненного цикла
### Функция Lifespan
Первое, на что стоит обратить внимание, это то, что мы определяем асинхронную функцию с `yield`. Это очень похоже на зависимости с `yield`.
Первое, что следует заметить, это то, что мы определяем асинхронную функцию с `yield`. Это очень похоже на зависимости с `yield`.
**Контекстный менеджер** в Python — это объект, который можно использовать в операторе `with`, например, функцию `open()` можно использовать как контекстный менеджер:
**Менеджер контекста** в Python - это нечто, что вы можете использовать в операторе `with`, например, `open()` может быть использован как менеджер контекста:
```Python
with open("file.txt") as file:
file.read()
```
В последних версиях Python есть также **асинхронный контекстный менеджер**. Вы будете использовать его с `async with`:
В последних версиях Python также существует **асинхронный менеджер контекста**. Вы бы использовали его с `async with`:
```Python
async with lifespan(app):
await do_stuff()
```
Когда вы создаете контекстный менеджер или асинхронный контекстный менеджер, как выше, он делает так, что, прежде чем войти в блок `with`, будет выполнен код перед `yield`, а после выхода из блока `with`, будет выполнен код после `yield`.
Когда вы создаете менеджер контекста или асинхронный менеджер контекста, как выше, его работа заключается в том, что, до входа в блок `with`, он выполняет код перед `yield`, и после выхода из блока `with`, он выполняет код после `yield`.
В нашем примере кода выше мы не используем его напрямую, но передаем его FastAPI для использования.
Параметр `lifespan` приложения `FastAPI` принимает **асинхронный контекстный менеджер**, так что мы можем передать ему наш новый асинхронный контекстный менеджер `lifespan`.
Параметр `lifespan` приложения `FastAPI` принимает **асинхронный менеджер контекста**, так что мы можем передать ему наш новый асинхронный менеджер контекста`lifespan`.
{* ../../docs_src/events/tutorial003.py hl[22] *}
@ -88,78 +88,78 @@ async with lifespan(app):
/// warning | Предупреждение
Рекомендуемый способ управления *запуском* и *завершением работы* — использовать параметр`lifespan` приложения `FastAPI`, как описано выше. Если вы предоставите параметр `lifespan`, обработчики событий `startup` и `shutdown` больше не будут вызываться. Используются либо `lifespan`, либо событийные обработчики, но не оба.
Рекомендуемый способ обработки *запуска* и *выключения* — это использование параметра`lifespan` приложения `FastAPI`, как описано выше. Если вы предоставите параметр `lifespan`, обработчики событий `startup` и `shutdown` больше не будут вызываться. Вы можете использовать либо `lifespan`, либо события, но не оба.
Вы, вероятно, можете пропустить эту часть.
Эту часть можно, вероятно, пропустить.
///
Существует альтернативный способ определения этой логики для выполнения в момент *запуска* и *завершения работы*.
Существует альтернативный способ определения логики, которая будет выполнена во время *запуска* и во время *выключения*.
Вы можете определить обработчики событий (функции), которые должны быть выполнены до запуска приложения, или при завершении работы приложения.
Вы можете определить обработчики событий (функции), которые необходимо выполнить до запуска приложения или при его остановке.
Эти функции могут быть объявлены с `async def` или обычными`def`.
Эти функции могут быть объявлены как `async def`, так и обычный`def`.
### Событие `startup`
Чтобы добавить функцию, которая должна выполняться перед началом работы приложения, объявите её с событием `"startup"`:
Чтобы добавить функцию, которую следует запустить перед запуском приложения, объявите ее с событием `"startup"`:
{* ../../docs_src/events/tutorial001.py hl[8] *}
В этом случае функция-обработчик события `startup` инициализирует "базу данных" элементов (это просто `dict`) с некоторыми значениями.
В этом случае функция-обработчик события `startup` инициализирует "базу данных" элементов (просто `dict`) с некоторыми значениями.
Можно добавить более одной функции-обработчика события.
Вы можете добавить более одной функции-обработчика события.
И ваше приложение не начнет принимать запросы, пока не завершатся все обработчики события `startup`.
И ваше приложение не начнет принимать запросы до тех пор, пока все обработчики событий `startup` не будут выполнены.
### Событие `shutdown`
Чтобы добавить функцию, которая должна выполняться при завершении работы приложения, объявите её с событием `"shutdown"`:
Чтобы добавить функцию, которую следует запустить при остановке приложения, объявите ее с событием `"shutdown"`:
{* ../../docs_src/events/tutorial002.py hl[6] *}
Здесь функция-обработчик события `shutdown` запишет текстовую строку`"Application shutdown"` в файл `log.txt`.
Здесь функция-обработчик события `shutdown` запишет строку текста`"Application shutdown"` в файл `log.txt`.
/// info | Информация
В функции `open()`, параметр `mode="a"` означает "добавление", то есть строка будет добавлена после того, что уже есть в файле, без перезаписи предыдущего содержимого.
В функции `open()`, `mode="a"` означает "добавить", так что строка будет добавлена после того, что уже есть в этом файле, без перезаписи предыдущего содержимого.
///
/// tip | Совет
Обратите внимание, что в этом случае мы используем стандартную функцию Python `open()`, которая взаимодействует с файлом.
Обратите внимание, что в данном случае мы используем стандартную функцию Python `open()`, которая взаимодействует с файлом.
Таким образом, это включает ввод/вывод (I/O), который требует "ожидания", чтобы данные были записаны на диск.
Таким образом, это включает в себя ввод/вывод (I/O), который требует "ожидания" для записи данных на диск.
Но `open()` не использует `async` и `await`.
Поэтому мы объявляем функцию-обработчик событий с использованием стандартного`def` вместо `async def`.
Поэтому мы объявляем функцию-обработчик события как стандартный`def` вместо `async def`.
///
### `startup` и `shutdown` вместе
Существует высокая вероятность того, что логика вашего *запуска* и *завершения работы* связана; возможно, вы хотите что-то начать, а затем завершить, получить ресурс, а потом его освободить и т.д.
Существует большая вероятность, что логика ваших *запуска* и *выключения* связана; возможно, вы хотите что-то запустить, а затем завершить, получить ресурс, а затем освободить его и т. д.
Выполнение этого в отдельных функциях, которые не разделяют логику или переменные, сложнее, поскольку вам нужно будет сохранять значения в глобальных переменных или использовать подобные трюки.
Делать это в раздельных функциях, которые не используют совместно логику или переменные, сложнее, так как придется сохранять значения в глобальных переменных или использовать подобные уловки.
По этой причине теперь рекомендуется вместо этого использовать `lifespan`, как объяснено выше.
Из-за этого теперь рекомендуется вместо этого использовать `lifespan`, как объяснено выше.
## Технические детали
Просто техническая деталь для любопытных. 🤓
Просто техническая деталь для любознательных гиков. 🤓
В ASGI технической спецификации это часть <ahref="https://asgi.readthedocs.io/en/latest/specs/lifespan.html"class="external-link"target="_blank">оглашения Жизненного Цикла</a>, и она определяет события `startup` и `shutdown`.
В основе, в технической спецификации ASGI, это часть <ahref="https://asgi.readthedocs.io/en/latest/specs/lifespan.html"class="external-link"target="_blank">Протокола Lifespan</a>, и он определяет события, называемые`startup` и `shutdown`.
/// info | Информация
Вы можете прочитать больше о обработчиках `lifespan` в Starlette в <ahref="https://www.starlette.io/lifespan/"class="external-link"target="_blank">документации Starlette's Lifespan</a>.
Вы можете прочитать больше об обработчиках `lifespan` в Starlette в <ahref="https://www.starlette.io/lifespan/"class="external-link"target="_blank">документации "Lifespan" Starlette</a>.
В том числе то, как справляться с состоянием жизненного цикла, которое может быть использовано в других областях вашего кода.
Включая информацию о том, как обрабатывать состояние lifespan, которое может быть использовано в других частях вашего кода.
///
## Подпрограммы
## Подприложения
🚨 Помните, что эти события жизненного цикла (startup и shutdown) будут выполнены только для основного приложения, а не для [Подпрограмм - Монтирования](sub-applications.md){.internal-link target=_blank}.
🚨 Помните, что эти события жизненного цикла (запуска и завершения) будут выполнены только для основного приложения, а не для [подприложений - монтировок](sub-applications.md){.internal-link target=_blank}.
Так как **FastAPI** основан на спецификации OpenAPI, вы получаете автоматическую совместимость со многими инструментами, включая автоматическую документацию API (предоставленную Swagger UI).
Так как **FastAPI** основан на спецификации OpenAPI, вы автоматически получаете совместимость со многими инструментами, включая автоматическую документацию API (предоставляемую Swagger UI).
Одним из преимуществ, не всегда очевидных, является возможность **генерировать клиентов** (иногда их называют <abbrtitle="Software Development Kits">**SDK**</abbr>) для вашего API на различных **языках программирования**.
Один конкретный плюс, который не всегда очевиден, заключается в том, что вы можете **сгенерировать клиентов** (иногда их называют <abbrtitle="Software Development Kits">**SDKs**</abbr> ) для вашего API для многих разных **языков программирования**.
## Генераторы клиентов OpenAPI
Существует множество инструментов для генерации клиентов из **OpenAPI**.
Распространённый инструмент - это <ahref="https://openapi-generator.tech/"class="external-link"target="_blank">OpenAPI Generator</a>.
Один из популярных инструментов — это <ahref="https://openapi-generator.tech/"class="external-link"target="_blank">OpenAPI Generator</a>.
Если вы создаёте **фронтенд**, отличной альтернативой является <ahref="https://github.com/hey-api/openapi-ts"class="external-link"target="_blank">openapi-ts</a>.
Если вы создаете **фронтенд**, очень интересной альтернативой является <ahref="https://github.com/hey-api/openapi-ts"class="external-link"target="_blank">openapi-ts</a>.
## Генераторы клиентов и SDK - спонсор
## Генераторы клиентов и SDK - спонсоры
Существуют также некоторые **поддерживаемые компаниями** генераторы клиентов и SDK на базе OpenAPI (FastAPI), которые в некоторых случаях могут предложить вам **дополнительные функции** поверх высококачественно сгенерированных SDK/клиентов.
Существуют также некоторые **поддерживаемые компаниями** генераторы клиентов и SDK, основанные на OpenAPI (FastAPI), в некоторых случаях они могут предложить вам **дополнительные функции** поверх высококачественных сгенерированных SDK/клиентов.
Некоторые из них также ✨ [**спонсируют FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, что обеспечивает продолжение и здоровое **развитие** FastAPI и его **экосистемы**.
Некоторые из них также ✨ [**спонсируют FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, что обеспечивает продолжительное и здоровое **развитие** FastAPI и его **экосистемы**.
Это показывает их истинную приверженность FastAPI и его **сообществу** (вам), ведь они не только хотят предоставить вам **хороший сервис**, но и хотят убедиться, что у вас есть **хороший и здоровый фреймворк**, FastAPI. 🙇
Это также демонстрирует их истинную приверженность FastAPI и его **сообществу** (вам), ведь они не только хотят предоставить вам **качественное обслуживание**, но и стремятся обеспечить вам **качественный и здоровый фреймворк**, FastAPI. 🙇
Обратите внимание, что *операции пути* определяют модели, которые они используют для запросов и ответов, используя модели `Item` и `ResponseMessage`.
Обратите внимание, что *операции пути* определяют модели, которые они используют для полезной нагрузки запроса и ответа, используя модели `Item` и `ResponseMessage`.
### Документация API
Если вы перейдёте к документации API, вы увидите, что она содержит**схемы** для данных, которые должны быть отправлены в запросах и получены в ответах:
Если вы перейдете к документации API, вы увидите, что в ней содержатся**схемы** для данных, которые должны быть отправлены в запросах и получены в ответах:
Для генерации клиентского кода вы можете использовать приложение командной строки`openapi-ts`, которое теперь будет установлено.
Чтобы сгенерировать код клиента, вы можете использовать командное приложение`openapi-ts`, которое теперь будет установлено.
Поскольку оно установлено в локальном проекте, вы, вероятно, не сможете вызывать эту команду напрямую, но сможете прописать её в файле`package.json`.
Поскольку оно установлено в локальном проекте, вы, вероятно, не сможете вызвать эту команду напрямую, но вы можете поместить её в свой файл`package.json`.
Эта команда сгенерирует код в `./src/client` и будет использовать `axios` (библиотеку HTTP для фронтенда).
Эта команда сгенерирует код в `./src/client` и будет использовать `axios` (фронтенд HTTP-библиотеку) для внутренних нужд.
### Попробуйте использовать клиентский код
### Попробуйте код клиента
Теперь вы можете импортировать и использовать клиентский код, это может выглядеть так, обратите внимание, что вы получите автодополнение для методов:
Теперь вы можете импортировать и использовать код клиента, он может выглядеть следующим образом, обратите внимание, что вы получаете автозавершение для методов:
Во многих случаях ваше приложение FastAPI будет более крупным, и, вероятно, вы будете использовать теги для разделения различных групп *операций пути*.
Во многих случаях ваше FastAPI-приложение будет больше, и вы, скорее всего, будете использовать теги для разделения разных групп *операций пути*.
Например, у вас может быть раздел для **элементов** и другой раздел для **пользователей**, и они могут быть отделены тегами:
Например, у вас может быть раздел для **товаров** и другой раздел для **пользователей**, и они могут быть разделены с помощью тегов:
...это потому, что генератор клиента использует внутренний **идентификатор операции** OpenAPI для каждой *операции пути*.
...это потому, что генератор клиента использует внутренний **идентификатор операции** OpenAPI для каждой *операции пути*.
OpenAPI требует, чтобы каждый идентификатор операции был уникальным для всех *операций пути*, поэтому FastAPI использует **имя функции**, **путь** и **HTTP метод/операцию** для генерации этого идентификатора операции, так как это позволяет убедиться, что идентификаторы операций уникальны.
OpenAPI требует, чтобы каждый идентификатор операции был уникальным среди всех *операций пути*, поэтому FastAPI использует **название функции**, **путь** и **метод/операцию HTTP** для генерации этого идентификатора операции, поскольку таким образом он может обеспечить уникальность идентификаторов операций.
Но я покажу вам, как это улучшить. 🤓
Но я покажу вам, как улучшить это. 🤓
## Настраиваемые идентификаторы операций и улучшенные имена методов
## Пользовательские идентификаторы операций и лучшие имена методов
Вы можете **изменить** способ генерации этих идентификаторов операций, чтобы сделать их проще и получить **более простые имена методов** в клиентах.
Вы можете **изменить** способ **генерации** этих идентификаторов операций, чтобы упростить их и получить **более простые названия методов** в клиентах.
В этом случае вам придётся обеспечить уникальность каждого идентификатора операции другим способом.
В этом случае вы должны обеспечить уникальность каждого идентификатора операции другим способом.
Например, вы можете удостовериться, что каждая *операция пути* имеет тег, а затем генерировать идентификатор операции на основе **тега** и **имени** операции пути (имени функции).
Например, вы можете убедиться, что каждая *операция пути* имеет тег, а затем генерировать идентификатор операции на основе **тега** и **названия операции пути** (названия функции).
### Настраиваемая функция генерации уникального идентификатора
### Пользовательская функция генерации уникального идентификатора
FastAPI использует **уникальный идентификатор** для каждой *операции пути*, он используется для **идентификатора операции**, а также для имен необходимых пользовательских моделей, для запросов или ответов.
FastAPI использует **уникальный ID** для каждой *операции пути*, он используется для **идентификатора операции**, а также для имен любых необходимых пользовательских моделей для запросов или ответов.
Вы можете настроить эту функцию. Она принимает `APIRoute` и возвращает строку.
Например, здесь используется первый тег (вероятно, у вас будет только один тег) и имя операции пути (имя функции).
Например, здесь используется первый тег (обычно у вас будет только один тег) и название *операции пути* (название функции).
Вы можете передать эту пользовательскую функцию в **FastAPI** как параметр`generate_unique_id_function`:
Вы можете передать эту пользовательскую функцию **FastAPI** в параметре`generate_unique_id_function`:
Как видите, имена методов теперь включают тег и имя функции, они больше не содержат информацию из URL пути и HTTP операции.
Как видите, названия методов теперь содержат тег, а затем имя функции, теперь они не включают информацию из URL-пути и HTTP-операции.
### Предварительная обработка спецификации OpenAPI для генерации клиента
### Предобработка спецификации OpenAPI для генератора клиентов
Сгенерированный код всё ещё содержит некоторую **дублированную информацию**.
Сгенерированный код все равно содержит некоторую **дублирующую информацию**.
Мы уже знаем, что этот метод связан с **items** (элементами), так как это слово содержится в `ItemsService` (взятом из тега), но у нас всё ещё есть название тега в начале имени метода. 😕
Мы уже знаем, что этот метод связан с **товарами (items)**, потому что это слово появилось в `ItemsService` (взято из тега), но у нас все равно есть имя тега в имени метода. 😕
Возможно, мы всё же захотим оставить его для OpenAPI в целом, так как это обеспечит уникальность идентификаторов операций.
Вероятно, мы все равно захотим сохранить это для OpenAPI в целом, так как это обеспечит **уникальность** идентификаторов операций.
Но для сгенерированного клиента мы могли бы**изменить** идентификаторы операций OpenAPI прямо перед генерацией клиентов, чтобы сделать имена методов более красивыми и **чистыми**.
Но для сгенерированного клиента мы можем**изменить** идентификаторы операций OpenAPI прямо перед генерацией клиентов, только чтобы сделать эти названия методов более приятными и **чистыми**.
Мы могли бы загрузить JSON OpenAPI в файл `openapi.json`, а затем могли бы **удалить этот префиксированный тег** при помощи скрипта, как здесь:
Мы можем загрузить OpenAPI JSON в файл `openapi.json`, а затем **удалить** этот префикс тега с помощью такого скрипта:
@ -216,11 +216,11 @@ FastAPI использует **уникальный идентификатор**
////
Благодаря этому идентификаторы операций будут переименованы из `items-get_items` в просто `get_items`, что позволит генератору клиента создать более простые имена методов.
Таким образом, идентификаторы операций будут переименованы из `items-get_items` в просто `get_items`, так генератор клиентов сможет генерировать более простые имена методов.
### Генерация TypeScript клиента с предварительно обработанным OpenAPI
### Генерация TypeScript-клиента с предобработанным OpenAPI
Теперь, так как конечный результат находится в файле `openapi.json`, вы можете изменить`package.json`, чтобы использовать этот локальный файл, например:
Теперь, так как конечный результат находится в файле `openapi.json`, вы измените файл`package.json`, чтобы использовать этот локальный файл, например:
```JSON hl_lines="7"
{
@ -240,22 +240,22 @@ FastAPI использует **уникальный идентификатор**
}
```
После генерации нового клиента вы теперь получите **чистые имена методов**, с всеми **автодополнениями**, **встроенными ошибками** и прочим:
После генерации нового клиента у вас теперь будут **чистые названия методов**, с всеми **подсказками автозавершения**, **ошибками на лету** и т.д.:
При использовании автоматически сгенерированных клиентов вы получите **автодополнение** для:
При использовании автоматически сгенерированных клиентов вы получите **автозавершение** для:
* Методов.
* Загружаемых данных в теле запроса, параметрах запроса и т. д.
* Полей в ответах.
* Полезной нагрузки запроса в теле, параметрах запроса и т.д.
* Полезной нагрузки ответа.
Вы также получите **встроенные ошибки** для всего.
Вы также получите **ошибки на лету** для всего.
И всякий раз, когда вы обновляете код серверной части и **перегенерируете** фронтенд, он будет иметь все новые *операции пути* как методы, старые будут удалены, и любые другие изменения будут отражены в сгенерированном коде. 🤓
И всякий раз, когда вы обновляете код на серверной стороне и **регенерируете** фронтенд, он будет содержать любые новые *операции пути* как методы, старые будут удалены, а любые другие изменения будут отражены в сгенерированном коде. 🤓
Это также значит, что если что-то изменилось, это будет **отражено** в клиентском коде автоматически. И если вы **соберёте** клиента, вы получите ошибку, если данные не совпадают.
Это также означает, что если что-то изменится, это будет **отражено** в клиентском коде автоматически. И если вы **соберёте** клиент, у него возникнет ошибка, если у вас возникнет **несоответствие** в используемых данных.
Таким образом, вы сможете **обнаружить многие ошибки** на ранней стадии цикла разработки, вместо того чтобы ждать, пока ошибки проявятся у ваших конечных пользователей в производстве, и затем пытаться выяснить, где находится проблема. ✨
Таким образом, вы **обнаружите множество ошибок** на раннем этапе цикла разработки, вместо того чтобы ждать, пока ошибки проявятся у ваших конечных пользователей в продакшне, и потом пытаться выяснить, в чем проблема. ✨
В основном учебнике вы прочитали, как добавить [Custom Middleware](../tutorial/middleware.md){.internal-link target=_blank} в ваше приложение.
В основном руководстве вы узнали, как добавить [пользовательский Middleware](../tutorial/middleware.md){.internal-link target=_blank} в ваше приложение.
Также вы узнали, как обрабатывать [CORS с помощью `CORSMiddleware`](../tutorial/cors.md){.internal-link target=_blank}.
А также вы узнали, как обрабатывать [CORS с использованием `CORSMiddleware`](../tutorial/cors.md){.internal-link target=_blank}.
В этой секции мы рассмотрим, как использовать другие middlewares.
В этом разделе мы увидим, как использовать другие middleware.
## Добавление ASGI middlewares
## Добавление ASGI middleware
Так как **FastAPI** основан на Starlette и реализует спецификацию <abbrtitle="Asynchronous Server Gateway Interface">ASGI</abbr>, вы можете использовать любое ASGI middleware.
Так как **FastAPI** основан на Starlette и реализует спецификацию <abbrtitle="Asynchronous Server Gateway Interface">ASGI</abbr>, вы можете использовать любой ASGI middleware.
Middleware не обязательно должен быть создан для FastAPI или Starlette, чтобы работать, до тех пор, пока он следует спецификации ASGI.
Для работы middleware не обязательно должны быть созданы специально для FastAPI или Starlette, достаточно того, чтобы они следовали спецификации ASGI.
В общем, ASGI middlewares - это классы, которые ожидают получить ASGI приложение в качестве первого аргумента.
В общем случае, ASGI middleware представляют собой классы, которые ожидают получение ASGI приложения в качестве первого аргумента.
Таким образом, в документации для сторонних ASGI middlewares вероятно, вас попросят сделать что-то похожее на:
Таким образом, в документации для сторонних ASGI middleware, скорее всего, будет указано сделать следующее:
Но FastAPI (на самом деле Starlette) предоставляет более простой способ сделать это, который гарантирует, что внутренние middleware будут правильно обрабатывать ошибки сервера и пользовательские обработчики исключений.
Но FastAPI (на самом деле Starlette) предоставляет более простой способ сделать это, обеспечивая правильную обработку ошибок сервера и работу пользовательских обработчиков ошибок.
Для этого используйте `app.add_middleware()` (как в примере для CORS).
`app.add_middleware()` принимает класс middleware в качестве первого аргумента и любые дополнительные аргументы, которые будут переданы в middleware.
`app.add_middleware()` принимает класс middleware в качестве первого аргумента и любые дополнительные аргументы, которые должны быть переданы в middleware.
## Встроенные middlewares
## Интегрированные middleware
**FastAPI** включает несколько middlewares для общих случаев использования, дальше мы рассмотрим как их использовать.
**FastAPI** включает несколько middleware для общих случаев использования, далее мы рассмотрим, как их использовать.
/// note | Технические детали
Для следующих примеров вы также могли бы использовать `from starlette.middleware.something import SomethingMiddleware`.
В следующих примерах вы также можете использовать `from starlette.middleware.something import SomethingMiddleware`.
**FastAPI** предоставляет несколько middlewares в `fastapi.middleware` просто для вашего удобства, как разработчика. Но большинство доступных middlewares приходят непосредственно из Starlette.
**FastAPI** предоставляет несколько middleware в `fastapi.middleware` исключительно для удобства разработчика. Но большинство доступных middleware поступает непосредственно из Starlette.
///
## `HTTPSRedirectMiddleware`
Обеспечивает, чтобы все входящие запросы были либо `https`, либо`wss`.
Обеспечивает, чтобы все входящие запросы были перенаправлены на `https` или`wss`.
Любой входящий запрос на `http` или `ws` будет перенаправлен на защищённую схему.
Любой входящий запрос на `http` или `ws` будет перенаправлен на защищенную схему.
Обеспечивает, чтобы все входящие запросы имели правильно установленный заголовок `Host`, чтобы защититься от атак посредством подмены заголовка HTTP Host.
Обеспечивает наличие корректно заданного HTTP-заголовка `Host` во всех входящих запросах для защиты от атак на основе HTTP-заголовка Host.
* `allowed_hosts` - Список доменных имён, которые должны быть разрешены в качестве имён хоста. Поддерживаются подстановочные домены, такие как `*.example.com`, для совпадения с поддоменами. Чтобы разрешить любое имя хоста, либо используйте `allowed_hosts=["*"]`, либо пропустите middleware.
* `allowed_hosts` - список доменных имен, которые разрешены в качестве имен хостов. Разрешены шаблоны доменов, такие как `*.example.com`, для соответствия поддоменам. Для разрешения всех имен хостов можно использовать `allowed_hosts=["*"]` или вовсе не добавлять middleware.
Если входящий запрос проходит проверку неправильно, то будет отправлен ответ`400`.
Если входящий запрос не валидируется, отправляется ответ с кодом`400`.
## `GZipMiddleware`
Обрабатывает GZipответы для любого запроса, который включает `"gzip"` в заголовке `Accept-Encoding`.
Обрабатывает GZip-ответы для любого запроса, который включает `"gzip"` в HTTP-заголовке `Accept-Encoding`.
Middleware обрабатывает как стандартные, так и потоковые ответы.
Middleware будет обрабатывать как стандартные, так и потоковые HTTP-ответы.
* `minimum_size` - Не использовать GZip для ответов, размер которых меньше этого минимума в байтах. По умолчанию `500`.
* `compresslevel` - Используется во время GZip сжатия. Это целое число в диапазоне от 1 до 9. Значение по умолчанию `9`. Меньшее значение приводит к более быстрому сжатию, но большим размерам файлов, в то время как большее значение приводит к более медленному сжатию, но меньшим размерам файлов.
* `minimum_size` - не выполнять GZip сжатие для HTTP-ответов, которые меньше этого минимального размера в байтах. По умолчанию `500`.
* `compresslevel` - используется во время GZip сжатия. Это число в диапазоне от 1 до 9. По умолчанию `9`. Меньшее значение приводит к более быстрому сжатию, но большему размеру файлов, в то время как большее значение приводит к более медленному сжатию, но меньшему размеру файлов.
* <ahref="https://github.com/encode/uvicorn/blob/master/uvicorn/middleware/proxy_headers.py"class="external-link"target="_blank">`ProxyHeadersMiddleware` от Uvicorn</a>
Чтобы увидеть другие доступные middlewares, ознакомьтесь с <ahref="https://www.starlette.io/middleware/"class="external-link"target="_blank">документацией по Middlewares от Starlette</a> и <ahref="https://github.com/florimondmanca/awesome-asgi"class="external-link"target="_blank">ASGI Awesome List</a>.
Чтобы увидеть другие доступные middleware, ознакомьтесь с <ahref="https://www.starlette.io/middleware/"class="external-link"target="_blank">документацией Starlette по Middleware</a> и <ahref="https://github.com/florimondmanca/awesome-asgi"class="external-link"target="_blank">списком ASGI Awesome List</a>.
Вы можете создать API с *операцией пути*, которая может инициировать запрос к *внешнему API*, созданному кем-то другим (возможно, тем же разработчиком, который будет *использовать* ваше API).
Вы можете создать API с *операцией пути*, которая может инициировать запрос к *внешнему API*, созданному кем-то другим (вероятно, тем же разработчиком, который будет *использовать* ваше API).
Процесс, который происходит, когда ваше API-приложение вызывает *внешний API*, называется "обратным вызовом" (callback). Потому что программа, написанная внешним разработчиком, отправляет запрос в ваше API, а затем ваше API делает "обратный вызов", отправляя запрос во *внешний API* (который, вероятно, был создан тем же разработчиком).
Процесс, который происходит, когда ваше API-приложение вызывает *внешний API*, называется "callback". Потому что ПО, написанное внешним разработчиком, отправляет запрос вашему API, а затем ваше API *отвечает*, отправляя запрос к *внешнему API* (который, возможно, был создан тем же разработчиком).
В этом случае вам может понадобиться документировать, как этот внешний API *должен* выглядеть. Какие *операции пути* он должен иметь, какое тело ожидать, какой ответ должен возвращать и т.д.
В этом случае, вы могли бы захотеть задокументировать, как этот внешний API *должен* выглядеть. Какие *операции пути* он должен содержать, какое тело ожидать, какой ответ должен возвращать и т.д.
## Приложение с обратными вызовами
## Приложение с callback'ами
Давайте посмотрим на всё это на примере.
Рассмотрим это на примере.
Представьте, что вы разрабатываете приложение, которое позволяет создавать счета-фактуры.
Представьте, что вы разрабатываете приложение, позволяющее создавать счета.
Эти счета-фактуры будут иметь `id`, `title` (необязательно), `customer` и `total`.
Эти счета будут иметь `id`, `title` (опционально), `customer` и `total`.
Пользователь вашего API (внешний разработчик) создаст счет-фактуру в вашем API с помощью POST-запроса.
Пользователь вашего API (внешний разработчик) создаст счет в вашем API с помощью POST-запроса.
Затем ваше API (давайте представим):
Затем ваше API будет (давайте представим):
* Отправит счет некоторому клиенту внешнего разработчика.
* Соберет деньги.
* Отправит уведомление обратно пользователю API (внешнему разработчику).
* Это будет сделано путем отправки POST-запроса (из *вашего API*) на какой-то *внешний API*, предоставленный тем внешним разработчиком (это и есть "обратный вызов").
* Отправлять счет какому-то клиенту внешнего разработчика.
* Собирать деньги.
* Отправлять уведомление обратно пользователю API (внешнему разработчику).
* Это будет сделано с помощью отправки POST-запроса (от *вашего API*) в некоторый *внешний API*, предоставленный этим внешним разработчиком (это и есть "callback").
## Обычное приложение **FastAPI**
Давайте сначала посмотрим, как будет выглядеть обычное API-приложение до добавления обратного вызова.
Сначала посмотрим, как обычное приложение API выглядело бы до добавления callback'а.
Оно будет иметь *операцию пути*, которая будет получать тело `Invoice`, и параметр запроса `callback_url`, который будет содержать URL для обратного вызова.
Оно будет иметь *операцию пути*, которая будет принимать `Invoice` в теле запроса и параметр запроса `callback_url`, который будет содержать URL для callback'а.
Эта часть довольно обычная, большая часть кода, вероятно, уже знакома вам:
Эта часть довольно обычная, и, вероятно, большая часть кода уже знакома вам:
Параметр запроса `callback_url` использует тип Pydantic <ahref="https://docs.pydantic.dev/latest/api/networks/"class="external-link"target="_blank">Url</a>.
Параметр запроса `callback_url` использует тип Url из Pydantic <ahref="https://docs.pydantic.dev/latest/api/networks/"class="external-link"target="_blank">Url</a>.
///
Единственное, что ново, это `callbacks=invoices_callback_router.routes` в качестве аргумента для декоратора *операции пути*. Мы посмотрим, что это такое дальше.
Единственное новое здесь — это `callbacks=invoices_callback_router.routes` как аргумент к *декоратору операции пути*. Дальше мы увидим, что это значит.
## Документирование обратного вызова
## Документирование callback'а
Фактический код обратного вызова будет сильно зависеть от вашего собственного API-приложения.
Фактический код callback'а будет сильно зависеть от вашего API-приложения.
И, вероятно, будет значительно отличаться для разных приложений.
И, вероятно, значительно изменится от одного приложения к другому.
Это может быть всего одна или две строчки кода, например:
Это может быть всего одна или две строчки кода, такие как:
Но, возможно, самая важная часть обратного вызова — это убедиться, что пользователь вашего API (внешний разработчик) корректно реализует *внешний API*, в соответствии с данными, которые *ваше API* собирается отправить в теле запроса для обратного вызова и т. д.
Но, возможно, наиболее важная часть callback'а — это убедиться, что пользователь вашего API (внешний разработчик) корректно реализует *внешний API*, согласно данным, которые *ваше API* собирается отправить в теле запроса callback'а и т.д.
Таким образом, что мы собираемся сделать дальше, это добавить код для документирования того, как этот *внешний API* должен выглядеть, чтобы принимать обратный вызов от *вашего API*.
Итак, следующее, что мы сделаем, это добавим код для документации, как этот *внешний API* должен выглядеть для получения callback'а от *вашего API*.
Эта документация будет отображаться в Swagger UI по адресу `/docs` в вашем API и она позволит внешним разработчикам знать, как построить *внешний API*.
Эта документация отобразится в интерфейсе Swagger по адресу `/docs` в вашем API и позволит внешним разработчикам узнать, как создать *внешний API*.
Этот пример не реализует сам обратный вызов (это может быть всего одна строка кода), только часть документации.
Этот пример не реализует сам callback (это может быть всего одна строка кода), только часть документации.
/// tip | Совет
/// tip | Подсказка
Фактический обратный вызов — это просто HTTP-запрос.
Фактический callback — это просто HTTP-запрос.
При самостоятельной реализации обратного вызова вы можете использовать что-то вроде <ahref="https://www.python-httpx.org"class="external-link"target="_blank">HTTPX</a> или <ahref="https://requests.readthedocs.io/"class="external-link"target="_blank">Requests</a>.
При реализации callback'а самостоятельно, вы можете использовать что-то вроде <ahref="https://www.python-httpx.org"class="external-link"target="_blank">HTTPX</a> или <ahref="https://requests.readthedocs.io/"class="external-link"target="_blank">Requests</a>.
///
## Написание кода документации для обратного вызова
## Написание кода документации для callback'а
Этот код не будет выполняться в вашем приложении, нам он нужен только для того, чтобы *документировать*, как этот*внешний API* должен выглядеть.
Этот код не будет выполнен в вашем приложении, нам нужно его только чтобы *документировать*, как *внешний API* должен выглядеть.
Но вы уже знаете, как легко создать автоматическую документацию для API с использованием**FastAPI**.
Но вы уже знаете, как легко создавать автоматическую документацию для API с **FastAPI**.
Итак, мы собираемся использовать эти знания, чтобы задокументировать, как *внешний API* должен выглядеть... создав *операции пути*, которые внешний API должен реализовать (те, которые ваше API будет вызывать).
Поэтому мы используем эти же знания, чтобы задокументировать, как *внешний API* должен выглядеть... создавая*операцию(ии) пути*, которые внешний API должен реализовать (те, которые ваше API вызовет).
/// tip | Совет
/// tip | Подсказка
При написании кода для документирования обратного вызова может быть полезно представить, что вы — это тот *внешний разработчик*. И что вы в данный момент реализуете *внешний API*, а не *ваше API*.
Когда пишете код для документации callback'а, может быть полезно представить, что вы — этот *внешний разработчик*. И что вы в данный момент реализуете *внешний API*, а не *ваше API*.
Временное принятие этой точки зрения (внешнего разработчика) может помочь вам почувствовать, что более очевидно, где разместить параметры, модель Pydantic для тела, для ответа и т. д. для этого *внешнего API*.
Временное принятие этой точки зрения (внешнего разработчика) поможет вам скорее определить, куда помещать параметры, Pydantic-модель для тела запроса, для ответа и т.д. для этого *внешнего API*.
///
### Создание `APIRouter` для обратного вызова
### Создание callback `APIRouter`
Сначала создайте новый `APIRouter`, который будет содержать один или несколько обратных вызовов.
Сначала создайте новый `APIRouter`, который будет содержать один или несколько callback'ов.
Существует 2 основных отличия от обычной *операции пути*:
Есть 2 основных отличия от обычной *операции пути*:
* Она не должна содержать никакого реального кода, потому что ваше приложение никогда не будет вызывать этот код. Он используется только для документирования *внешнего API*. Поэтому функция может просто содержать `pass`.
* *Путь* может содержать<ahref="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#key-expression"class="external-link"target="_blank">выражение OpenAPI 3</a> (см. ниже), где могут использоваться переменные с параметрами и частями исходного запроса, отправленного в *ваше API*.
* Реальный код не нужен, так как ваше приложение никогда не вызовет этот код. Он используется только для документирования *внешнего API*. Поэтому функция может просто содержать `pass`.
* В *пути* может содержаться<ahref="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'а
*Путь* обратного вызова может содержать <ahref="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'а может содержать <ahref="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`:
В этом случае это `str`:
```Python
"{$callback_url}/invoices/{$request.body.id}"
```
Таким образом, если пользователь вашего API (внешний разработчик) отправляет запрос в *ваше API* на:
Таким образом, если пользователь вашего API (внешний разработчик) отправляет запрос к *вашему API* по адресу:
Обратите внимание, как URL для обратного вызова содержит URL, полученный в виде параметра запроса в `callback_url` (`https://www.external.org/events`) и также `id` счета-фактуры из JSON-тела (`2expen51ve`).
Обратите внимание, как callback URL включает URL, полученный как параметр запроса в `callback_url` (`https://www.external.org/events`), и также `id` счета из тела JSON (`2expen51ve`).
///
### Добавление маршрутизатора для обратных вызовов
### Добавление router для callback'ов
На этом этапе у вас есть нужные *операции пути для обратных вызовов* (те, которые *внешний разработчик* должен реализовать во *внешнем API*) в созданном выше маршрутизаторе для обратных вызовов.
На данном этапе у вас есть *операция(ии) пути для callback'а*, необходимые (те, которые *внешний разработчик* должен реализовать во *внешнем API*) в callback router, который вы создали выше.
Теперь используйте параметр `callbacks` в *декораторе операции пути вашего API*, чтобы передать атрибут `.routes` (на самом деле это просто `list` маршрутов/*операций путей*) из этого маршрутизатора для обратных вызовов:
Теперь используйте параметр `callbacks` в *декораторе операции пути вашего API*, чтобы передать атрибут `.routes` (который на самом деле является просто `list`'ом маршрутов/*операций пути*) из этого callback router:
Обратите внимание, что вы передаете не сам маршрутизатор (`invoices_callback_router`) в `callback=`, а атрибут `.routes`, как в `invoices_callback_router.routes`.
Обратите внимание, что вы не передаете сам router (`invoices_callback_router`) в `callback=`, а передаете атрибут `.routes`, как в `invoices_callback_router.routes`.
///
### Проверка документации
Теперь вы можете запустить своё приложение и перейти на <ahref="http://127.0.0.1:8000/docs"class="external-link"target="_blank">http://127.0.0.1:8000/docs</a>.
Теперь вы можете запустить ваше приложение и перейти на <ahref="http://127.0.0.1:8000/docs"class="external-link"target="_blank">http://127.0.0.1:8000/docs</a>.
Вы увидите вашу документацию, включая раздел "Callbacks" для вашей *операции пути*, который показывает, как должен выглядеть *внешний API*:
Существуют случаи, когда вы хотите сообщить вашим **пользователям API**, что ваше приложение может вызвать *их* приложение (отправив запрос) с некоторыми данными, обычно чтобы **уведомить** о каком-то типе **события**.
Существуют случаи, когда вы хотите сообщить пользователям вашего API, что ваше приложение может отправлять запросы их приложениям с данными, обычно для уведомления о каком-то событии.
Это означает, что вместо обычного процесса, когда ваши пользователи отправляют запросы вашему API, это **ваш API** (или ваше приложение), который может **отправлять запросы их системе** (их API, их приложению).
Это означает, что вместо обычного процесса, когда ваши пользователи отправляют запросы вашему API, это ваш API (или ваше приложение) может отправлять запросы их системе (их API, их приложению).
Обычно это называется **вебхук**.
Это обычно называется **webhook**.
## Шаги вебхуков
Процесс обычно заключается в том, что **вы определяете** в своем коде, какое сообщение вы будете отправлять, **тело запроса**.
Процесс обычно состоит в том, что **вы определяете** в вашем коде сообщение, которое вы собираетесь отправить, **тело запроса**.
Вы также каким-то образом определяете, в какие **моменты** ваше приложение будет отправлять эти запросы или события.
Вы также каким-либо образом определяете, в какие **моменты** ваше приложение будет отправлять эти запросы или события.
А **ваши пользователи** каким-то образом (например, в веб-интерфейсе где-то) определяют **URL**, на который ваше приложение должно отправлять эти запросы.
И **ваши пользователи** каким-либо образом (например, в веб-панели управления) определяют **URL**, куда ваше приложение должно отправлять эти запросы.
Вся **логика**относительно того, как регистрировать URL для вебхуков и писать код для отправки этих запросов, зависит от вас. Вы пишете это так, как хотите, в **вашем собственном коде**.
Вся **логика**регистрации URL-адресов для вебхуков и кода для фактической отправки этих запросов зависит от вас. Вы пишете это в **собственном коде** так, как хотите.
## Документирование вебхуков с **FastAPI** и OpenAPI
## Документирование вебхуков с помощью **FastAPI** и OpenAPI
С помощью **FastAPI**, используя OpenAPI, вы можете определить имена этих вебхуков, типы HTTP операций, которые ваше приложение может отправлять (например, `POST`, `PUT` и т.д.), и тела **запросов**, которые ваше приложение будет отправлять.
С помощью **FastAPI** и OpenAPI вы можете определить названия этих вебхуков, типы HTTP операций, которые ваше приложение может отправлять (например, `POST`, `PUT` и т.д.), и **тела запросов**, которые ваше приложение будет отправлять.
Это может значительно облегчить вашим пользователям **реализацию их API** для приема ваших **вебхуков**, они даже могут автоматически сгенерировать часть своего собственного кода API.
Это может значительно упростить вашим пользователям **внедрение их API** для получения ваших **вебхуков**, они даже могут автоматически сгенерировать часть своего API кода.
/// info | Информация
Вебхуки доступны в OpenAPI 3.1.0 и выше, поддерживаются FastAPI `0.99.0` и выше.
Вебхуки доступны в OpenAPI 3.1.0 и выше, поддерживаемые FastAPI `0.99.0` и выше.
///
## Приложение с вебхуками
Когда вы создаете приложение **FastAPI**, существует атрибут `webhooks`, который вы можете использовать для определения *вебхуков*, так же, как вы определяете *операции с путями*, например, с помощью `@app.webhooks.post()`.
Когда вы создаете приложение **FastAPI**, есть атрибут `webhooks`, который вы можете использовать для определения *вебхуков* так же, как вы бы определяли *операции пути*, например с помощью `@app.webhooks.post()`.
Вебхуки, которые вы определите, окажутся в **OpenAPI** схеме и в автоматическом **документированном интерфейсе**.
Вебхуки, которые вы определяете, попадут в **схему OpenAPI** и в **автоматическую документацию UI**.
/// info | Информация
Объект `app.webhooks` на самом деле просто `APIRouter`, тот же тип, который вы бы использовали при структурировании вашего приложения с несколькими файлами.
Объект `app.webhooks` на самом деле является `APIRouter`, того же типа, который вы бы использовали при структурировании своего приложения с множеством файлов.
///
Обратите внимание, что с вебхуками вы на самом деле не объявляете *путь* (как`/items/`), текст, который вы передаете, является просто **идентификатором** вебхука (именем события), например, в `@app.webhooks.post("new-subscription")` имя вебхука -`new-subscription`.
Обратите внимание, что с вебхуками вы на самом деле не объявляете *путь* (например,`/items/`), текст, который вы передаете, является просто **идентификатором** вебхука (название события), например в `@app.webhooks.post("new-subscription")`, имя вебхука —`new-subscription`.
Это потому, что предполагается, что **ваши пользователи**будут определять фактический **путь URL**, куда они хотят получать запрос вебхука другим способом (например, через веб-интерфейс).
Это потому, что ожидается, что **ваши пользователи** определят фактический **путь URL**, куда они хотят получать запрос webhook, каким-то другим образом (например, в веб-панели управления).
### Проверьте документацию
Теперь вы можете запустить ваше приложение и перейти по ссылке<ahref="http://127.0.0.1:8000/docs"class="external-link"target="_blank">http://127.0.0.1:8000/docs</a>.
Теперь вы можете запустить ваше приложение и перейти по адресу<ahref="http://127.0.0.1:8000/docs"class="external-link"target="_blank">http://127.0.0.1:8000/docs</a>.
Вы увидите, что в вашей документации есть обычные *операции пути*, а теперь и некоторые **вебхуки**:
Вы увидите, что ваша документация содержит обычные *операции пути* и теперь также некоторые **вебхуки**:
### Использование имени функции *операции пути*как operationId
### Использование имени функции *операции пути*в качестве operationId
Если вы хотите использовать имена функций своих API в качестве `operationId`, вы можете перебрать все из них и переопределить каждый `operation_id` для их *операций пути* используя `APIRoute.name`.
Если вы хотите использовать имена ваших функций API в качестве `operationId`, вы можете перебрать их все и переопределить `operation_id` каждой *операции пути*, используя `APIRoute.name`.
Вы должны сделать это после добавления всех своих *операций пути*.
Необходимо сделать это после добавления всех ваших *операций пути*.
Если вы вручную вызываете `app.openapi()`, вам следует обновить `operationId` перед этим.
Если вы вручную вызываете `app.openapi()`, обновите `operationId` до этого.
///
/// warning | Предупреждение
Если вы так делаете, вам нужно убедиться, что каждая из ваших *функций операций пути* имеет уникальное имя.
Если вы это делаете, необходимо удостовериться, что каждая из ваших *функций операций пути* имеет уникальное имя.
Даже если они находятся в разных модулях (файлах Python).
@ -38,51 +38,51 @@
## Исключение из OpenAPI
Чтобы исключить *операцию пути* из создаваемой схемы OpenAPI (и, следовательно, из систем автоматической документации), используйте параметр `include_in_schema` и установите его в`False`:
Чтобы исключить *операцию пути* из генерируемой схемы OpenAPI (и таким образом, из систем автоматической документации), используйте параметр `include_in_schema` и установите его значение как`False`:
Вы, вероятно, уже видели, как объявлять `response_model` и `status_code` для *операции пути*.
Вы, вероятно, уже знаете, как объявить `response_model` и `status_code` для *операции пути*.
Это определяет метаданные о основном ответе *операции пути*.
Это определяет метаданные об основном ответе *операции пути*.
Вы также можете объявить дополнительные ответы с их моделями, статусами кодов и т.д.
Вы также можете объявить дополнительные ответы с их моделями, статус-кодами и т.д.
Здесь есть целая глава в документации об этом, вы можете прочитать ее в разделе [Дополнительные ответы в OpenAPI](additional-responses.md){.internal-link target=_blank}.
В документации есть целая глава об этом, которую вы можете прочитать в разделе [Дополнительные ответы в OpenAPI](additional-responses.md){.internal-link target=_blank}.
## OpenAPI Extra
## Дополнения OpenAPI
Когда вы объявляете *операцию пути* в своем приложении, **FastAPI** автоматически генерирует соответствующие метаданные об этой *операции пути* для включения в схему OpenAPI.
Когда вы объявляете *операцию пути* в вашем приложении, **FastAPI** автоматически генерирует соответствующие метаданные об этой *операции пути*, чтобы включить их в схему OpenAPI.
/// note | Технические детали
В спецификации OpenAPI это называется <ahref="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#operation-object"class="external-link"target="_blank">Operation Object</a>.
В спецификации OpenAPI это называется <ahref="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` и т.д.
Это включает в себя`tags`, `parameters`, `requestBody`, `responses` и т.д.
Эта OpenAPI схема, специфичная для*операции пути*, обычно генерируется автоматически **FastAPI**, но вы также можете расширить ее.
Эта схема OpenAPI для данной*операции пути* обычно генерируется автоматически **FastAPI**, но ее можно и расширить.
/// tip | Совет
Это низкоуровневая точка расширения.
Это точка низкоуровневого расширения.
Если вам нужно только объявить дополнительные ответы, более удобный способ сделать это — с [Дополнительные ответы в OpenAPI](additional-responses.md){.internal-link target=_blank}.
Если вам нужно только объявить дополнительные ответы, более удобный способ сделать это — через [Дополнительные ответы в OpenAPI](additional-responses.md){.internal-link target=_blank}.
///
@ -90,15 +90,15 @@
### Расширения OpenAPI
Этот `openapi_extra` может быть полезен, например, для объявления [OpenAPI Extensions](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions):
Этот `openapi_extra` может быть полезен, например, чтобы объявить [Расширения OpenAPI](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions):
И если вы посмотрите на результат OpenAPI (по адресу `/openapi.json` в вашем API), вы увидите свое расширение как часть конкретной *операции пути* тоже:
И если вы увидите результат OpenAPI (по адресу `/openapi.json` в вашем API), вы увидите ваше расширение также как часть конкретной *операции пути*:
Словарь в `openapi_extra` будет глубоко объединен с автоматически сгенерированной схемой OpenAPI для *операции пути*.
Таким образом, вы сможете добавить дополнительные данные к автоматически сгенерированной схеме.
Так вы можете добавить дополнительные данные в автоматически сгенерированную схему.
Например, вы могли бы решить считать и проверять запрос с помощью вашего собственного кода, не используя автоматические функции FastAPI с Pydantic, но при этом вы могли бы захотеть определить запрос в схеме OpenAPI.
Например, вы можете решить сами считывать и валидировать запрос без использования автоматических функций FastAPI с Pydantic, но вы все равно можете захотеть определить запрос в схеме OpenAPI.
Вы могли бы сделать это с помощью `openapi_extra`:
В этом примере мы не объявляли модель Pydantic. Фактически, тело запроса даже не <abbrtitle="преобразовано из какого-то простого формата, как байты, в объекты Python">проанализировано</abbr> как JSON, оно читается напрямую как `bytes`, и функция `magic_data_reader()` будет отвечать за его парсинг каким-то образом.
В этом примере мы не объявили никакой Pydantic-модели. На самом деле тело запроса даже не <abbrtitle="преобразовано из какого-либо простого формата, как байты, в Python-объекты">проанализировано</abbr> как JSON, оно считывается напрямую как `bytes`, и функция `magic_data_reader()` будет отвечать за его парсинг.
Тем не менее, мы можем объявить ожидаемую схему для тела запроса.
### Индивидуальный тип содержимого OpenAPI
### Пользовательский тип содержимого OpenAPI
Используя эту же хитрость, вы могли бы использовать модель Pydantic для определения JSON Schema, которое затем включается в пользовательскую секцию OpenAPI схемы для *операции пути*.
Используя этот же трюк, вы можете использовать Pydantic-модель для определения JSON-схемы, которая затем будет включена в пользовательский раздел схемы OpenAPI для *операции пути*.
И вы могли бы сделать это, даже если тип данных в запросе не является JSON.
И вы можете сделать это даже если тип данных в запросе не JSON.
Например, в этом приложении мы не используем интегрированную функциональность FastAPI для извлечения JSON Schema из моделей Pydantic, ни автоматической валидации для JSON. Фактически, мы объявляем тип содержимого запроса как YAML, а не JSON:
Например, в этом приложении мы не используем интегрированные функции FastAPI для извлечения JSON-схемы из Pydantic-моделей и автоматической валидации для JSON. В самом деле, мы объявляем тип содержимого запроса как YAML, а не JSON:
//// tab | Pydantic v2
@ -167,15 +167,15 @@
/// info | Информация
В версии Pydantic 1 метод для получения JSON Schema для модели назывался `Item.schema()`, в версии Pydantic 2 этот метод называется `Item.model_json_schema()`.
В версии Pydantic 1 метод для получения JSON-схемы для модели назывался `Item.schema()`, в версии Pydantic 2 метод называется `Item.model_json_schema()`.
///
Тем не менее, хотя мы и не используем стандартную интегрированную функциональность, мы все равно используем модель Pydantic для ручной генерации JSON Schema для данных, которые мы хотим получить в формате YAML.
Тем не менее, хотя мы не используем стандартную интегрированную функциональность, мы все же используем модель Pydantic, чтобы вручную сгенерировать JSON-схему для данных, которые мы хотим получить в YAML.
Затем мы используем запрос напрямую и извлекаем тело как `bytes`. Это означает, что FastAPI даже не пытается парсить полезную нагрузку запроса как JSON.
Затем мы используем запрос напрямую и извлекаем тело как `bytes`. Это означает, что FastAPI даже не будет пытаться парсить содержимое запроса как JSON.
А в нашем коде мы парсим содержимое YAML напрямую и затем снова используем ту же модель Pydantic для валидации содержимого YAML:
И затем в нашем коде мы парсим это YAML-содержимое напрямую, и затем снова используем ту же модель Pydantic для валидации содержимого YAML:
//// tab | Pydantic v2
@ -191,14 +191,14 @@
/// info | Информация
В версии Pydantic 1 метод для парсинга и валидации объекта назывался `Item.parse_obj()`, в версии Pydantic 2 этот метод называется `Item.model_validate()`.
В версии Pydantic 1 метод для парсинга и валидации объекта назывался `Item.parse_obj()`, в версии Pydantic 2 метод называется `Item.model_validate()`.
///
/// tip | Совет
Здесь мы используем ту же самую модель Pydantic.
Здесь мы повторно используем ту же модель Pydantic.
Но таким же образом, мы могли бы валидировать ее и другим способом.
Но таким же образом можно было бы валидировать ее каким-то другим способом.
И затем вы можете вернуть любой объект, который вам нужен, как вы обычно делаете (например, `dict`, модель базы данных и т.д.).
После этого вы можете вернуть любой объект, который вам нужен, как обычно (например, `dict`, модель базы данных и т.д.).
И если вы объявили `response_model`, он все равно будет использован для фильтрации и конвертации объекта, который вы вернули.
Если вы объявили `response_model`, он все равно будет использован для фильтрации и преобразования объекта, который вы вернули.
**FastAPI** использует этот *временный* ответ для извлечения заголовков (а также cookies и кода состояния) и помещает их в окончательный ответ, содержащий значение, которое вы вернули, отфильтрованное с помощью любого`response_model`.
**FastAPI** использует этот *временный* ответ для извлечения HTTP-заголовков (также cookie и статус-код ответа) и помещает их в окончательный ответ, который содержит значение, которое вы вернули и которое фильтруется любым`response_model`.
Вы также можете объявить параметр `Response` в зависимостях и установить заголовки (и cookies) в них.
Вы также можете объявить параметр `Response` в зависимостях и установить HTTP-заголовки (и cookie) в них.
## Возврат `Response` напрямую
## Непосредственный возврат `Response`
Вы также можете добавить заголовки, когда возвращаете `Response` напрямую.
Вы также можете добавить HTTP-заголовки при непосредственном возврате объекта типа `Response`.
Создайте ответ, как описано в разделе [Return a Response Directly](response-directly.md){.internal-link target=_blank} и передайте заголовки в качестве дополнительного параметра:
Создайте ответ, как описано в разделе [Непосредственный возврат ответа](response-directly.md){.internal-link target=_blank}, и передайте HTTP-заголовки в качестве дополнительного параметра:
Вы также можете использовать `from starlette.responses import Response` или `from starlette.responses import JSONResponse`.
Вы также могли бы использовать `from starlette.responses import Response` или `from starlette.responses import JSONResponse`.
**FastAPI** предоставляет те же `starlette.responses`, что и `fastapi.responses`, просто для вашего удобства, разработчика. Но большинство доступных ответов поступают непосредственно из Starlette.
**FastAPI** предоставляет те же `starlette.responses`, что и `fastapi.responses`, для удобства разработчика. Но большинство доступных ответов поступает непосредственно из Starlette.
И так как `Response` может часто использоваться для установки заголовков и cookies, **FastAPI** также предоставляет его в `fastapi.Response`.
И так как `Response` может часто использоваться для установки HTTP-заголовков и cookie, **FastAPI** также предоставляет его в `fastapi.Response`.
///
## Пользовательские заголовки
Имейте в виду, что пользовательские проприетарные заголовки могут быть добавлены<ahref="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers"class="external-link"target="_blank">с использованием префикса 'X-'</a>.
Имейте в виду, что можно добавлять проприетарные пользовательские HTTP-заголовки<ahref="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`, задокументированный в <ahref="https://www.starlette.io/middleware/#corsmiddleware"class="external-link"target="_blank">документации Starlette о CORS</a>.
Но если у вас есть пользовательские заголовки, которые вы хотите, чтобы клиент в браузере мог видеть, вам нужно добавить их в ваши CORS-настройки (прочтите больше в разделе [CORS (Cross-Origin Resource Sharing)](../tutorial/cors.md){.internal-link target=_blank}), используя параметр `expose_headers`, документированный в <ahref="https://www.starlette.io/middleware/#corsmiddleware"class="external-link"target="_blank">документации Starlette по CORS</a>.
Когда вы попробуете открыть URL в первый раз (или нажмёте кнопку "Execute" в документации), браузер запросит у вас ввод имени пользователя и пароля:
Когда вы попытаетесь открыть URL в первый раз (или нажмете кнопку "Execute" в документации) браузер спросит вас имя пользователя и пароль:
<imgsrc="/img/tutorial/security/image12.png">
@ -30,15 +30,15 @@
Вот более полный пример.
Используйте зависимость, чтобы проверить, правильны ли имя пользователя и пароль.
Используйте зависимость, чтобы проверить, правильные ли имя пользователя и пароль.
Для этого используйте стандартный модуль Python <ahref="https://docs.python.org/3/library/secrets.html"class="external-link"target="_blank">`secrets`</a>, чтобы проверить имя пользователя и пароль.
Для этого используйте стандартный модуль Python <ahref="https://docs.python.org/3/library/secrets.html"class="external-link"target="_blank">`secrets`</a> для проверки имени пользователя и пароля.
`secrets.compare_digest()`должен принимать `bytes` или `str`, который содержит только символы ASCII (те, которые в английском языке), это значит, что он не будет работать с такими символами, как `á`, как в `Sebastián`.
`secrets.compare_digest()`требует `bytes` или `str`, содержащий только ASCII-символы (те, что на английском), это значит, что он не будет работать с символами, такими как `á`, например в `Sebastián`.
Чтобы обработать это, мы сначала преобразуем `username` и `password` в `bytes`, кодируя их с помощью UTF-8.
Чтобы с этим справиться, мы сначала конвертируем `username` и `password` в `bytes`, кодируя их в UTF-8.
Затем мы можем использовать `secrets.compare_digest()`, чтобы убедиться, что `credentials.username` равен `"stanleyjobson"`, а `credentials.password` равен `"swordfish"`.
Затем мы можем использовать `secrets.compare_digest()` для гарантии, что `credentials.username` равен `"stanleyjobson"`, а `credentials.password` равен `"swordfish"`.
if not (credentials.username == "stanleyjobson") or not (credentials.password == "swordfish"):
# Возвращаем какую-то ошибку
# Return some error
...
```
Но, используя `secrets.compare_digest()`, это будет безопасно от типа атак, называемых "атаки по времени".
Но, используя `secrets.compare_digest()`, это будет защищено от атак под названием "тайминговые атаки".
### Атаки по времени
### Тайминговые атаки
Что же такое "атака по времени"?
Что такое "тайминговая атака"?
Представьте, что некоторые злоумышленники пытаются угадать имя пользователя и пароль.
Представим, что некоторая группа злоумышленников пытается угадать имя пользователя и пароль.
И они отправляют запрос с именем пользователя `johndoe` и паролем `love123`.
Они отправляют запрос с именем пользователя `johndoe` и паролем `love123`.
Тогда Python-код в вашем приложении будет аналогичен чему-то такому:
Тогда Python-код в вашем приложении будет эквивалентен чему-то вроде:
```Python
if "johndoe" == "stanleyjobson" and "love123" == "swordfish":
...
```
Но в тот момент, когда Python сравнивает первую `j` в `johndoe` с первой `s` в `stanleyjobson`, он вернёт `False`, потому что уже знает, что эти строки не одинаковы, считая, что "нет смысла тратить больше вычислений на сравнение оставшихся букв". И ваше приложение скажет "Неверное имя пользователя или пароль".
Но в тот момент, когда Python сравнивает первую `j` в `johndoe` с первой `s` в `stanleyjobson`, он вернет `False`, так как уже знает, что эти две строки не одинаковы, считая, что "нет смысла тратить больше вычислительных ресурсов на сравнение остальных букв". И ваше приложение скажет "Неправильное имя пользователя или пароль".
Но затем злоумышленники пробуют с именем пользователя `stanleyjobsox` и паролем `love123`.
Но затем злоумышленники попробуют с именем пользователя `stanleyjobsox` и паролем `love123`.
И ваш код в приложении делает что-то похожее:
И ваш код приложения делает что-то вроде:
```Python
if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish":
...
```
Python должен будет сравнить весь `stanleyjobso` в обоих `stanleyjobsox` и `stanleyjobson` перед тем, как осознать, что эти две строки не одинаковы. Поэтому для возвращения "Неверное имя пользователя или пароль" потребуется немного больше времени.
Python должен будет сравнить весь `stanleyjobso` в `stanleyjobsox` и `stanleyjobson`, чтобы понять, что строки не одинаковы. Так что это займет дополнительные микросекунды, чтобы ответить "Неправильное имя пользователя или пароль".
#### Время ответа помогает злоумышленникам
В этот момент, заметив, что сервер потратил несколько микросекунд дольше для отправки ответа "Неверное имя пользователя или пароль", злоумышленники будут знать, что они угадали _что-то_ правильно, некоторые начальные буквы были правильными.
В этот момент, заметив, что серверу понадобилось немного больше времени, чтобы отправить ответ "Неправильное имя пользователя или пароль", злоумышленники узнают, что они что-то угадали, некоторые начальные буквы были правильными.
И затем они могут попробовать снова, зная, что это, вероятно, что-то больше похожее на `stanleyjobsox`, чем на `johndoe`.
И тогда они могут попробовать снова, зная, что это, вероятно, что-то более похожее на `stanleyjobsox`, чем на `johndoe`.
#### "Профессиональная" атака
Конечно, злоумышленники не будут пробовать все это вручную, они напишут программу, чтобы сделать это, возможно, с тысячами или миллионами тестов в секунду. И они получат только одну дополнительную правильную букву за раз.
Конечно, злоумышленники не будут делать все это вручную, они напишут программу, чтобы делать это, возможно, с тысячами или миллионами тестов в секунду. И они будут получать лишь одну правильную букву за раз.
Но, делая это, за несколько минут или часов злоумышленники могут угадать правильное имя пользователя и пароль с "помощью" нашего приложения, просто используя время, взятое на ответ.
Но, делая так, за несколько минут или часов злоумышленники могли бы угадать правильное имя пользователя и пароль, с "помощью" нашего приложения, просто используя время, затраченное на ответ.
#### Исправьте это с помощью `secrets.compare_digest()`
Но в нашем коде мы фактически используем `secrets.compare_digest()`.
Вкратце, это займет одинаковое время для сравнения`stanleyjobsox` с `stanleyjobson`, как и для сравнения`johndoe` с `stanleyjobson`. И то же самое для пароля.
Вкратце, это займет одинаковое время, чтобы сравнить`stanleyjobsox` с `stanleyjobson`, как и `johndoe` с `stanleyjobson`. И то же самое для пароля.
Таким образом, используя `secrets.compare_digest()` в вашем коде приложения, оно будет безопасным от всего этого множества атак на безопасность.
Таким образом, используя `secrets.compare_digest()` в коде вашего приложения, оно будет защищено от этого целого спектра атак на безопасность.
### Возвращение ошибки
### Возврат ошибки
После обнаружения, что учетные данные неверны, верните `HTTPException` со статусным кодом 401 (тем же, что возвращается, когда учетные данные не предоставлены) и добавьте заголовок `WWW-Authenticate`, чтобы заставить браузер снова показать приглашение о вводе данных:
После обнаружения, что учетные данные неверны, верните `HTTPException` с кодом состояния 401 (тот же, что возвращается, когда учетные данные не предоставлены) и добавьте заголовок `WWW-Authenticate`, чтобы браузер снова отобразил приглашение для входа:
Существуют дополнительные функции для обеспечения безопасности, помимо тех, которые рассмотрены в [Учебнике - Руководстве пользователя: Безопасность](../../tutorial/security/index.md){.internal-link target=_blank}.
Существуют дополнительные функции для обеспечения безопасности, кроме тех, которые рассмотрены в [Руководстве пользователя: Безопасность](../../tutorial/security/index.md){.internal-link target=_blank}.
/// tip | Совет
Следующие разделы **не обязательно "продвинутые"**.
И возможно, что решение для вашего случая использования находится в одном из них.
И возможно, что для вашего случая решения находятся в одном из них.
///
## Сначала прочитайте Учебник
## Сначала прочтите Руководство
Следующие разделы предполагают, что вы уже прочитали основной [Учебник - Руководство пользователя: Безопасность](../../tutorial/security/index.md){.internal-link target=_blank}.
Следующие разделы предполагают, что вы уже прочитали основное [Руководство пользователя: Безопасность](../../tutorial/security/index.md){.internal-link target=_blank}.
Все они основаны на тех же концепциях, но позволяют добавить дополнительные функции.
Все они основаны на тех же концепциях, но позволяют использовать некоторые дополнительные функции.
Вы можете использовать OAuth2 области непосредственно с **FastAPI**, они интегрированы для бесшовной работы.
Вы можете использовать OAuth2 scope напрямую с **FastAPI**, они интегрированы для работы без проблем.
Это позволит вам иметь более детализированную систему разрешений, следуя стандарту OAuth2, интегрированному в ваше приложение OpenAPI (и API-документацию).
Это позволит вам иметь более тонкую систему разрешений, следуя стандарту OAuth2, интегрированную в ваше OpenAPI приложение (и документы API).
OAuth2 с областями — это механизм, используемый многими крупными поставщиками аутентификации, такими как Facebook, Google, GitHub, Microsoft, Twitter и др. Они используют его, чтобы предоставить пользователям и приложениям доступ к определённым разрешениям.
OAuth2 с scope — это механизм, используемый многими крупными провайдерами аутентификации, такими как Facebook, Google, GitHub, Microsoft, Twitter и т.д. Они используют его для предоставления определенных разрешений пользователям и приложениям.
Каждый раз, когда вы "входите через" Facebook, Google, GitHub, Microsoft, Twitter, это приложение использует OAuth2 с областями.
Каждый раз, когда вы "входите с помощью" Facebook, Google, GitHub, Microsoft, Twitter, это приложение использует OAuth2 с scope.
В этом разделе вы узнаете, как управлять аутентификацией и авторизацией с помощью тех же OAuth2 областей в вашем приложении на**FastAPI**.
В этом разделе вы увидите, как управлять аутентификацией и авторизацией с использованием того же OAuth2 с scope в вашем приложении**FastAPI**.
/// warning | Предупреждение
Это более или менее продвинутый раздел. Если вы только начинаете, вы можете его пропустить.
Это более или менее сложный раздел. Если вы только начинаете, вы можете пропустить его.
Вам не обязательно нужны OAuth2 области, и вы можете обрабатывать аутентификацию и авторизацию так, как вам удобно.
Вы не обязательно нуждаетесь в OAuth2 scope, и можете обрабатывать аутентификацию и авторизацию, как вам угодно.
Но OAuth2 с областями можно красиво интегрировать в ваш API (с OpenAPI) и в вашу API-документацию.
Но OAuth2 с scope может быть прекрасно интегрирован в ваш API (с OpenAPI) и в ваши документы API.
Тем не менее, вы всё равно соблюдаете эти области или любое другое требование безопасности/авторизации, как вам нужно, в вашем коде.
Тем не менее, вы все равно должны применять эти scope, или любые другие требования к безопасности/авторизации, так как вам нужно, в вашем коде.
Во многих случаях, OAuth2 с областями может быть излишен.
В многих случаях, OAuth2 с scope может быть избыточными.
Но если вы знаете, что он вам нужен, или вы просто любопытны, продолжайте читать.
Но если вы знаете, что они вам нужны, или вы заинтересовались, продолжайте чтение.
///
## OAuth2 области и OpenAPI
## OAuth2 scope и OpenAPI
Спецификация OAuth2 определяет "области" как список строк, разделённых пробелами.
Спецификация OAuth2 определяет "scope" как список строк, разделенных пробелами.
Содержимое каждой из этих строк может иметь любой формат, но не должно содержать пробелов.
Эти области представляют собой "разрешения".
Эти scope представляют собой "разрешения".
В OpenAPI (например, в API-документации) вы можете определить "схемы безопасности".
В OpenAPI (например, в документах API) вы можете определить "схемы безопасности".
Когда одна из этих схем безопасности использует OAuth2, вы также можете определить и использовать области.
Когда одна из этих схем безопасности использует OAuth2, вы также можете объявлять и использовать scope.
Каждая "область" — это просто строка (без пробелов).
Каждый "scope" — это просто строка (без пробелов).
Они обычно используются для определения конкретных разрешений безопасности, например:
Обычно они используются для объявления определенных разрешений безопасности, например:
* `users:read` или `users:write` — это общие примеры.
* `instagram_basic` используется Facebook/Instagram.
* `users:read` или `users:write` — это распространенные примеры.
* `instagram_basic` используется Facebook/Instagram.
* `https://www.googleapis.com/auth/drive` используется Google.
/// info | Информация
В OAuth2 "область" — это просто строка, которая декларирует необходимое специфическое разрешение.
В OAuth2 "scope" — это просто строка, указывающая на конкретные требуемые разрешения.
Неважно, есть ли у неё другие символы, такие как `:`, или если это URL.
Не имеет значения, есть ли в нем другие символы, такие как `:`, или если это URL.
Эти детали зависят от реализации.
Эти детали специфичны для реализации.
Для OAuth2 они просто строки.
///
## Глобальный обзор
## Общее представление
Во-первых, быстро рассмотрим части, которые изменяются в примерах в основном **Учебнике - Руководстве пользователя** для [OAuth2 с паролем (и хешированием), носитель с JWT токенами](../../tutorial/security/oauth2-jwt.md){.internal-link target=_blank}. Теперь используя OAuth2 области:
Сначала давайте быстро посмотрим на части, которые изменяются по сравнению с примерами в основном **Учебнике - Руководстве пользователя** для [OAuth2 с Паролем (и хешированием), Bearer с JWT токенами](../../tutorial/security/oauth2-jwt.md){.internal-link target=_blank}. Теперь используя OAuth2 scope:
Поскольку мы теперь объявляем эти области, они будут отображаться в API-документации, когда вы войдете/авторизуетесь.
Поскольку мы теперь объявляем эти scope, они будут отображаться в документации API, когда вы входите/авторизируетесь.
И вы сможете выбрать, к каким областям вы хотите предоставить доступ: `me` и `items`.
И вы сможете выбрать, к каким scope вы хотите дать доступ: `me` и `items`.
Это тот же механизм, который используется при предоставлении разрешений при входе через Facebook, Google, GitHub и т.д.:
Это тот же механизм, который используется, когда вы предоставляете разрешения при входе в Facebook, Google, GitHub и т.д:
<imgsrc="/img/tutorial/security/image11.png">
## JWT токен с областями
## JWT токен с scope
Теперь измените *path operation*, чтобы вернуть запрошенные области.
Теперь модифицируйте токен *операции пути*, чтобы вернуть запрашиваемые scope.
Мы всё ещё используем тот же `OAuth2PasswordRequestForm`. Он включает собственность `scopes` со `списком``str`, с каждой областью, полученной в запросе.
Мы по-прежнему используем тот же `OAuth2PasswordRequestForm`. Он включает свойство `scopes` с `list` из `str`, в котором содержатся все scope, полученные в запросе.
И мы возвращаем области как часть JWT токена.
И мы возвращаем scope как часть JWT токена.
/// danger | Опасность
Для простоты, здесь мы просто добавляем области, полученные напрямую в токен.
Для упрощения, здесь мы просто добавляем полученные scope непосредственно в токен.
Но в вашем приложении, для безопасности, вы должны убедиться, что добавляете только те области, которые пользователь действительно может иметь, или те, которые вы предопределили.
Но в вашем приложении, с целью безопасности, вы должны удостовериться, что добавляете только те scope, которые пользователь действительно может иметь, или те, которые вы заранее определили.
## Объявление областей в *path operations* и зависимостях
## Объявление scope в *операциях пути* и зависимостях
Теперь мы объявляем, что *path operation* для `/users/me/items/` требует область`items`.
Теперь мы объявляем, что *операция пути* для `/users/me/items/` требует scope`items`.
Для этого мы импортируем и используем `Security` из `fastapi`.
Вы можете использовать `Security` для объявления зависимостей (так же, как `Depends`), но `Security` также получает параметр `scopes` со списком областей (строк).
Вы можете использовать `Security` для объявления зависимостей (так же как `Depends`), но `Security` также принимает параметр `scopes` со списком scope (строк).
В этом случае мы передаем функцию зависимости `get_current_active_user` в `Security` (так же, как мы бы делали с `Depends`).
В данном случае, мы передаем функцию зависимости `get_current_active_user` в `Security` (так же как мы бы делали это с `Depends`).
Но мы также передаем `список` областей, в этом случае только одну область: `items` (их могло бы быть больше).
Но мы также передаем `list` с scope, в этом случае только с одним scope: `items` (хотя их может быть больше).
И функция зависимости `get_current_active_user` также может объявлять подзависимости, не только с помощью `Depends`, но и с помощью `Security`. Объявляя свою подзависимую функцию (`get_current_user`), и дополнительные требования к областям.
И функция зависимости `get_current_active_user` также может объявлять подзависимости, не только с `Depends`, но и с `Security`. Объявляя свою подзависимость (`get_current_user`), а также требования к scope.
В этом случае требуется область `me` (могло бы требоваться более одной области).
В этом случае требуется scope `me` (могут требоваться и другие scope).
/// note | Примечание
Вам не обязательно нужно добавлять разные области в разных местах.
Вам не обязательно нужно добавлять разные scope в разные места.
Мы делаем это здесь, чтобы продемонстрировать, как **FastAPI** обрабатывает области, объявленные на разных уровнях.
Мы делаем это здесь, чтобы продемонстрировать, как **FastAPI** обрабатывает scope, объявленные на разных уровнях.
///
@ -128,27 +128,27 @@ OAuth2 с областями — это механизм, используемы
/// info | Технические детали
`Security` на самом деле является подклассом `Depends`, и имеет только один дополнительный параметр, который мы увидим позже.
`Security` на самом деле является подклассом `Depends`, и у него есть всего один дополнительный параметр, который мы рассмотрим позже.
Но используя `Security` вместо `Depends`, **FastAPI**распознает, что можно объявлять области безопасности, использовать их внутри и документировать API с помощью OpenAPI.
Но используя `Security` вместо `Depends`, **FastAPI**будет знать, что он может объявить scope безопасности, использовать их внутри и документировать API с помощью OpenAPI.
Но когда вы импортируете `Query`, `Path`, `Depends`, `Security` и другие из `fastapi`, это на самом деле функции, которые возвращают специальные классы.
///
## Использование `SecurityScopes`
## Используйте `SecurityScopes`
Теперь обновите зависимость `get_current_user`.
Эта функция используется приведёнными выше зависимостями.
Это та зависимость, которая используется выше.
Здесь мы используем ту же схему OAuth2, которую создали ранее, объявляя её как зависимость: `oauth2_scheme`.
Вот где мы используем ту же схему OAuth2, которую создали ранее, объявляя ее как зависимость: `oauth2_scheme`.
Поскольку эта функция зависимости сама по себе не имеет требований к областям, мы можем использовать `Depends` с `oauth2_scheme`, нам не нужно использовать `Security`, когда нам не нужно указывать области безопасности.
Поскольку эта функция зависимости не имеет собственных требований к scope, мы можем использовать `Depends` с `oauth2_scheme`, нам не нужно использовать `Security`, когда нам не нужно указывать scope безопасности.
Мы также объявляем специальный параметр типа `SecurityScopes`, импортированный из `fastapi.security`.
Также мы объявляем специальный параметр типа `SecurityScopes`, импортированный из `fastapi.security`.
Этот класс `SecurityScopes`аналогичен `Request` (в `Request` использовался объект запроса напрямую).
Этот класс `SecurityScopes`похож на `Request` (`Request` использовался для получения объекта запроса напрямую).
@ -156,119 +156,119 @@ OAuth2 с областями — это механизм, используемы
Параметр `security_scopes` будет типа `SecurityScopes`.
Он будет иметь свойство `scopes` со списком всех областей, требуемых самой функцией и всеми зависимостями, использующими её как подзависимость. Это значит, все "зависящие"... это может звучать запутанно, ниже объясняется подробнее.
У него есть свойство `scopes` со списком, содержащим все scope, требуемые как сама зависимость, так и все зависимости, которые используют это как подзависимость. Это значит, все "зависимые"... это может звучать запутанно, но это объясняется снова позже.
Объект `security_scopes` (класса `SecurityScopes`) также предоставляет атрибут `scope_str` с одной строкой, содержащей эти области, разделённые пробелами (мы его используем).
Объект `security_scopes` (класса `SecurityScopes`) также предоставляет атрибут `scope_str` с одной строкой, содержащей эти scope, разделенные пробелами (мы собираемся использовать его).
Мы создаём `HTTPException`, который можем использовать (`raise`) позже в некоторых точках.
Мы создаем `HTTPException`, который мы можем использовать (`raise`) позже в нескольких повторяющихся моментах.
В этом исключении мы включаем требуемые области (если они есть) в виде строки, разделённой пробелами (используя `scope_str`). Мы помещаем эту строку, содержащую области, в заголовок`WWW-Authenticate` (это часть спецификации).
В этом исключении мы включаем требуемые scope (если они есть) в виде строки, разделенной пробелами (используя `scope_str`). Мы помещаем эту строку, содержащую scope, в HTTP-заголовке`WWW-Authenticate` (это часть спецификации).
Мы проверяем, что получили `username`, и извлекаем области.
Мы проверяем, что получили `username`, и извлекаем scope.
А затем мы валидируем эти данные с помощью модели Pydantic (отлавливая исключение `ValidationError`), и если у нас есть ошибка чтения JWT токена или валидирования данных с помощью Pydantic, мы возбуждаем созданное ранее `HTTPException`.
Затем мы валидируем эти данные с помощью Pydantic-модели (обрабатывая исключение `ValidationError`), и если мы получаем ошибку при чтении JWT токена или валидации данных с Pydantic, мы вызываем `HTTPException`, который создали ранее.
Для этого мы обновляем модель Pydantic `TokenData`, добавив новое свойство`scopes`.
Для этого мы обновляем Pydantic-модель `TokenData` с новым свойством`scopes`.
Валидируя данные с помощью Pydantic, мы можем убедиться, что у нас есть, например, именно `список``str` с областями и `str` с `username`.
Валидируя данные с помощью Pydantic, мы можем убедиться, что у нас, например, точно есть `list` из `str` с scope и `str` с `username`.
Вместо, например, `dict`, или чего-то ещё, что могло бы в какой-то момент сломать приложение, создавая риск безопасности.
Вместо, например, `dict`, или чего-то еще, так как это может сломать приложение в какой-то момент позже, создавая угрозу безопасности.
Мы также проверяем, что у нас есть пользователь с этим именем пользователя, и если нет, мы возбуждаем то же исключение, которое создали ранее.
Мы также проверяем, что у нас есть пользователь с этим именем пользователя, и если нет, вызываем то же самое исключение, созданное ранее.
Теперь проверяем, что все области, требуемые этой зависимостью и всеми зависимыми (включая *path operations*), включены в области, предоставленные в полученном токене, в противном случае возбуждаем `HTTPException`.
Теперь мы проверяем, все ли требуемые scope, этой зависимостью и всеми зависимыми (включая *операции пути*), включены в предоставленные токеном scope, в противном случае вызываем `HTTPException`.
Для этого используем `security_scopes.scopes`, который содержит `список` со всеми этими областями как `str`.
Для этого мы используем `security_scopes.scopes`, который содержит `list` со всеми этими scope как `str`.
Давайте снова рассмотрим это дерево зависимостей и области.
Давайте еще раз рассмотрим это дерево зависимостей и scope.
Поскольку зависимость `get_current_active_user` имеет подзависимость `get_current_user`, область `"me"`, объявленная в `get_current_active_user`, будет включена в `список` требуемых областей в `security_scopes.scopes`, переданный`get_current_user`.
Поскольку зависимость `get_current_active_user` имеет подзависимость на `get_current_user`, scope `"me"`, объявленный в `get_current_active_user`, будет включен в список требуемых scope в `security_scopes.scopes`, переданном в`get_current_user`.
*Path operation* также объявляет область, `"items"`, так что это тоже будет в `списке``security_scopes.scopes`, переданный`get_current_user`.
Сама *операция пути* также объявляет scope, `"items"`, так что он также будет в списке `security_scopes.scopes`, передаваемом в`get_current_user`.
Вот как выглядит иерархия зависимостей и областей:
Вот как выглядит иерархия зависимостей и scope:
* *Path operation*`read_own_items` имеет:
* Требуемые области`["items"]` с зависимостью:
* У *операции пути*`read_own_items`:
* Требуемые scope`["items"]` с зависимостью:
* `get_current_active_user`:
* Функция зависимости `get_current_active_user` имеет:
* Требуемые области`["me"]` с зависимостью:
* Требуемые scope`["me"]` с зависимостью:
* `get_current_user`:
* Функция зависимости `get_current_user` имеет:
* Нет требуемых собственной области.
* Зависимость, использующую`oauth2_scheme`.
* У функции зависимости `get_current_user`:
* Нет требуемых scope.
* Зависимость, использующая`oauth2_scheme`.
* Параметр `security_scopes` типа `SecurityScopes`:
* Этот параметр `security_scopes` имеет свойство `scopes` с `списком`, содержащим все выше указанные области, так что:
* `security_scopes.scopes` будет содержать `["me", "items"]` для *path operation*`read_own_items`.
* `security_scopes.scopes` будет содержать `["me"]` для *path operation*`read_users_me`, потому что она объявлена в зависимости `get_current_active_user`.
* `security_scopes.scopes` будет содержать `[]` (ничего) для *path operation*`read_system_status`, потому что она не объявляет никакой `Security` с `scopes`, а её зависимость `get_current_user` также не объявляет никаких`scopes`.
* У этого параметра `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 | Подсказка
/// tip | Совет
Самое важное и "магическое" здесь то, что `get_current_user` будет иметь различный список `scopes` для проверки для каждого *path operation*.
Самое важное и "магическое" здесь то, что `get_current_user` будет иметь разный список `scopes` для проверки для каждой *операции пути*.
Всё зависит от `scopes`, объявленных в каждом *path operation* и каждой зависимости в дереве зависимостей для этого конкретного *path operation*.
Все зависит от `scopes`, объявленных в каждой *операции пути* и каждой зависимости в дереве зависимостей для данной конкретной *операции пути*.
///
## Больше деталей об`SecurityScopes`
## Подробности о`SecurityScopes`
Вы можете использовать `SecurityScopes` в любой точке, и в нескольких местах, не обязательно на "корневой" зависимости.
Вы можете использовать `SecurityScopes` в любой точке и в нескольких местах, он не должен быть на уровне "корневой" зависимости.
Оно всегда будет иметь области безопасности, объявленные в текущих `Security` зависимостях и все зависимые для **этого конкретного***path operation* и **этого конкретного** дерева зависимостей.
Он будет всегда иметь scope безопасности, объявленные в текущих зависимостях `Security` и всех зависимых для **конкретно этой***операции пути* и **конкретно этого** дерева зависимостей.
Поскольку `SecurityScopes` будут иметь все области, объявленные зависимыми, вы можете использовать его для проверки, что токен имеет требуемые области в центральной функции зависимости, а затем объявить различные требования к областям в различных *path operations*.
Поскольку `SecurityScopes` будут иметь все scope, заявленные зависимыми, вы можете использовать его для проверки того, что токен имеет требуемые scope в центральной функции зависимости, а затем объявлять разные требования к scope в разных *операциях пути*.
Они будут проверяться независимо для каждого *path operation*.
Они будут проверяться независимо для каждой *операции пути*.
## Проверьте это
Если откроете API-документацию, вы можете пройти аутентификацию и указать, какие области хотите авторизовать.
Если вы откроете документацию API, вы можете аутентифицироваться и указать, какие scope вы хотите авторизовать.
<imgsrc="/img/tutorial/security/image11.png">
Если вы не выберете ни одной области, вы будете "аутентифицированы", но при попытке получить доступ к `/users/me/` или `/users/me/items/` вы получите ошибку, говоря, что у вас недостаточно разрешений. Вы всё ещё сможете получить доступ к `/status/`.
Если вы не выберете ни один scope, вы будете "аутентифицированы", но при попытке доступа к `/users/me/` или `/users/me/items/` вы получите ошибку, сообщающую, что у вас недостаточно разрешений. Вы все равно сможете получить доступ к `/status/`.
И если вы выберете область `me`, но не`items`, вы сможете получить доступ к `/users/me/`, но не к `/users/me/items/`.
И если вы выберете scope `me`, но не scope`items`, вы сможете получить доступ к `/users/me/`, но не к `/users/me/items/`.
Вот что произойдёт с приложениями третьих сторон, которые пытались бы получить доступ к одной из этих *path operations* с токеном, предоставленным пользователем, в зависимости от того, сколько разрешений пользователь дал приложению.
Это именно то, что произойдет с сторонним приложением, которое пытается получить доступ к одной из этих *операций пути* с токеном, предоставленным пользователем, в зависимости от того, сколько разрешений пользователь дал приложению.
## О сторонних интеграциях
В этом примере мы используем "парольный" поток OAuth2.
В этом примере мы используем OAuth2 "парольный" поток.
Это уместно, когда мы входим в наше собственное приложение, вероятно, с нашим собственным интерфейсом.
Это уместно, когда мы входим в наше собственное приложение, вероятно, с нашим собственным фронтендом.
Потому что мы можем доверять ему получение `username` и `password`, так как мы его контролируем.
Но если вы создаёте OAuth2 приложение, к которому будут подключаться другие (то есть, если вы создаёте поставщика аутентификации, эквивалентного Facebook, Google, GitHub и т.д.), вы должны использовать один из других потоков.
Но если вы строите приложение OAuth2, к которому будут подключаться другие (т.е. если вы создаете провайдер аутентификации, эквивалентный Facebook, Google, GitHub и т.д.), вы должны использовать один из других потоков.
Наиболее распространенный — это неявный поток.
Наиболее распространенным является поток implicit.
Наиболее безопасный — это поток с кодом, но его сложнее реализовать, так как он требует больше шагов. Из-за сложности многие провайдеры в конечном итоге рекомендуют неявный поток.
Наиболее безопасным является поток с кодом, но его сложнее реализовать, так как он требует больше шагов. Поскольку он более сложен, многие провайдеры в конечном итоге предлагают поток implicit.
/// note | Примечание
Часто каждый поставщик аутентификации называет свои потоки по-разному, чтобы сделать это частью их бренда.
Часто каждый провайдер аутентификации называет свои потоки по-разному, чтобы сделать это частью своего бренда.
Но в конце концов, они реализуют тот же стандарт OAuth2.
Но в конечном итоге они реализуют тот же стандарт OAuth2.
///
**FastAPI** включает утилиты для всех этих потоков аутентификации OAuth2 в `fastapi.security.oauth2`.
## `Security` в бедператора `dependencies`
## `Security` в `dependencies` декоратора
Так же, как вы можете определить `список``Depends` в параметре `dependencies` там бедператора (как объясняется в [Зависимости в бедператорaх path operation](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), вы также можете использовать `Security` с `scopes` там.
Таким же образом, как вы можете определять `list` из `Depends` в параметре `dependencies` декоратора (как объяснено в [Dependencies в декораторах операций пути](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), вы также можете использовать `Security` с `scopes` там.
Во многих случаях вашему приложению могут потребоваться внешние настройки или конфигурации, например, секретные ключи, учетные данные для базы данных, учетные данные для сервисов электронной почты и так далее.
Во многих случаях вашим приложениям могут понадобиться внешние настройки или конфигурации, например, секретные ключи, учетные данные базы данных, учетные данные для почтовых сервисов и т.д.
Большинство из этих настроек переменные (могут изменяться), как, например, URL базы данных. И многие могут быть конфиденциальными, как секреты.
Большинство этих настроек являются изменяемыми (могут изменяться), как, например, URL-ы баз данных. Многие из них могут быть чувствительными, как, например, секреты.
По этой причине их обычно предоставляют в переменных окружения, которые считываются приложением.
По этой причине их обычно предоставляют в виде переменных окружения, которые считываются приложением.
/// tip | Совет
Чтобы понять переменные окружения, вы можете прочитать [Переменные окружения](../environment-variables.md){.internal-link target=_blank}.
Чтобы понять, что такое переменные окружения, можете прочитать [Переменные окружения](../environment-variables.md){.internal-link target=_blank}.
///
## Типы и валидация
Эти переменные окружения могут обрабатывать только текстовые строки, так как они находятся вне Python и должны быть совместимы с другими программами и остальной системой (и даже с различными операционными системами, такими как Linux, Windows, macOS).
Эти переменные окружения могут обрабатывать только текстовые строки, так как они являются внешними для Python и должны быть совместимы с другими программами и остальной системой (и даже с разными операционными системами, такими как Linux, Windows, macOS).
Это значит, что любое значение, считанное в Python из переменной окружения, будет `str`, и любая конвертация в другой тип или любая валидация должна выполняться в коде.
Это означает, что любое значение, считанное в Python из переменной окружения, будет типа `str`, и любое преобразование в другой тип или валидация должны осуществляться в коде.
## Pydantic `Settings`
К счастью, Pydantic предоставляет отличную утилиту для работы с этими настройками, поступающими из переменных окружения, с помощью<ahref="https://docs.pydantic.dev/latest/concepts/pydantic_settings/"class="external-link"target="_blank">Pydantic: Управление настройками</a>.
К счастью, Pydantic предоставляет отличную утилиту для работы с этими настройками, поступающими из переменных окружения, используя<ahref="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`:
Сначала убедитесь, что вы создали ваше [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активировали его и затем установили пакет `pydantic-settings`:
<divclass="termy">
@ -35,7 +35,7 @@ $ pip install pydantic-settings
</div>
Он также включен при установке `all`-дополнений с:
Этот пакет также включен, если вы устанавливаете все зависимости с:
<divclass="termy">
@ -48,17 +48,17 @@ $ pip install "fastapi[all]"
/// info | Информация
В Pydantic v1 он был включен в основной пакет. Теперь он распространяется как независимый пакет, чтобы вы могли выбрать, устанавливать его или нет, если вам эта функциональность не нужна.
В Pydantic v1 он поставлялся с основным пакетом. Теперь он распространяется как отдельный пакет, чтобы вы могли выбрать, устанавливать его или нет, если вам не нужна эта функциональность.
///
### Создание объекта `Settings`
Импортируйте `BaseSettings` из Pydantic и создайте подкласс, аналогично модели Pydantic.
Импортируйте `BaseSettings` из Pydantic и создайте подкласс, как это делается с Pydantic-моделью.
Так же, как с моделями Pydantic, вы объявляете атрибуты класса с аннотациями типов и, возможно, значениями по умолчанию.
Так же, как и с Pydantic-моделями, вы объявляете атрибуты класса с аннотациями типов и, возможно, значениями по умолчанию.
Вы можете использовать все те же функции и инструменты валидации, что и для моделей Pydantic, такие как различные типы данных и дополнительные проверки с помощью`Field()`.
Вы можете использовать все те же функции валидации и инструменты, которые используете в Pydantic-моделях, например, разные типы данных и дополнительные валидации с`Field()`.
//// tab | Pydantic v2
@ -70,7 +70,7 @@ $ pip install "fastapi[all]"
/// info | Информация
В Pydantic v1 вы бы импортировали `BaseSettings` непосредственно из `pydantic` вместо `pydantic_settings`.
В Pydantic v1 вы импортировали бы`BaseSettings` непосредственно из `pydantic` вместо `pydantic_settings`.
///
@ -80,13 +80,13 @@ $ pip install "fastapi[all]"
/// tip | Совет
Если вы хотите что-то быстрое для копирования и вставки, не используйте этот пример, воспользуйтесь последним ниже.
Если вам нужно что-то быстрое для копирования и вставки, не используйте этот пример, используйте последний ниже.
///
Затем, когда вы создадите экземпляр этого класса `Settings` (в данном случае в объекте `settings`), Pydantic будет читать переменные окружения без учета регистра, так что переменная в верхнем регистре `APP_NAME`будет прочитана для атрибута `app_name`.
Когда вы создаете экземпляр этого класса `Settings` (в данном случае, в объекте `settings`), Pydantic будет считывать переменные окружения без учета регистра, так что переменная в верхнем регистре `APP_NAME`все еще будет считана для атрибута `app_name`.
Затем он преобразует и проверит данные. Таким образом, когда вы используете этот объект `settings`, у вас будут данные тех типов, которые вы объявили (например, `items_per_user` будет `int`).
Затем он выполнит преобразование и валидацию данных. Таким образом, когда вы используете этот объект `settings`, у вас будут данные тех типов, которые вы объявили (например, `items_per_user` будет типа`int`).
### Использование `settings`
@ -96,7 +96,7 @@ $ pip install "fastapi[all]"
### Запуск сервера
Затем вы бы запустили сервер, передавая конфигурации в качестве переменных окружения, например, вы могли бы установить `ADMIN_EMAIL` и `APP_NAME` с помощью:
Далее вы можете запустить сервер, передавая конфигурации как переменные окружения, например, вы можете установить `ADMIN_EMAIL` и `APP_NAME` следующим образом:
<divclass="termy">
@ -110,43 +110,43 @@ $ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.p
/// tip | Совет
Чтобы установить несколько переменных окружения для одной команды, просто разделите их пробелом и укажите все перед командой.
Чтобы установить несколько переменных окружения для одной команды, просто разделите их пробелами и укажите перед командой.
///
И затем`admin_email` будет установлен на `"deadpool@example.com"`.
Тогда настройка`admin_email` будет установлена в`"deadpool@example.com"`.
`app_name` будет `"ChimichangApp"`.
Настройка `app_name` будет иметь значение`"ChimichangApp"`.
А `items_per_user` сохранит свое значение по умолчанию `50`.
А `items_per_user` сохранит свое значение по умолчанию — `50`.
## Настройки в другом модуле
Вы можете поместить эти настройки в другом файле модуля, как вы видели в [Большие приложения - Несколько файлов](../tutorial/bigger-applications.md){.internal-link target=_blank}.
Вы можете поместить эти настройки в другой файл-модуль, как вы видели в [Более крупные приложения - Несколько файлов](../tutorial/bigger-applications.md){.internal-link target=_blank}.
Вам также понадобится файл `__init__.py`, как вы видели в [Большие приложения - Несколько файлов](../tutorial/bigger-applications.md){.internal-link target=_blank}.
Вам также нужен будет файл `__init__.py`, как вы видели в [Более крупные приложения - Несколько файлов](../tutorial/bigger-applications.md){.internal-link target=_blank}.
///
## Настройки в зависимости
В некоторых случаях может быть полезно предоставлять настройки из зависимости, вместо того, чтобы иметь глобальный объект с`settings`, который используется везде.
В некоторых случаях может быть полезно предоставлять настройки из зависимости, вместо использования глобального объекта`settings`, который используется везде.
Это может быть особенно полезно во время тестирования, поскольку очень легко переопределить зависимость с вашими собственными настраиваемыми настройками.
Это может быть особенно полезно во время тестирования, так как очень легко переопределить зависимость вашими собственными настройками.
### Файл конфигурации
Из предыдущего примера, ваш файл `config.py` может выглядеть так:
Исходя из предыдущего примера, ваш файл `config.py` может выглядеть так:
В переопределении зависимости мы устанавливаем новое значение для `admin_email` при создании нового объекта`Settings`, а затем возвращаем этот новый объект.
В переопределении зависимости мы устанавливаем новое значение для `admin_email`, создавая новый объект`Settings`, а затем возвращаем этот новый объект.
Затем мы можем протестировать, что он используется.
## Чтение файла `.env`
Если у вас много настроек, которые, возможно, часто меняются, может быть полезно поместить их в файл и затем считать их как переменные окружения.
Если у вас есть множество настроек, которые могут часто меняться, возможно, в разных окружениях, может быть полезно поместить их в файл и затем считывать их из него, как если бы они были переменными окружения.
Эта практика настолько распространена, что у нее есть название, эти переменные окружения обычно размещаются в файл `.env`, и файл называется "dotenv".
Эта практика достаточно распространена, чтобы у нее было название, эти переменные окружения обычно помещаются в файл `.env`, и этот файл называется "dotenv".
/// tip | Совет
Файл, начинающийся с точки (`.`), является скрытым файлом в системах, подобных Unix, таких как Linux и macOS.
Файл, начинающийся с точки (`.`), является скрытым файлом в системах, похожих на Unix, таких как Linux и macOS.
Но на самом деле файл с настройками dotenv не обязательно должен иметь такое имя.
Но файл dotenv не обязательно должен иметь именно такое имя.
///
Pydantic поддерживает чтение из этих типов файлов с использованием внешней библиотеки. Вы можете узнать больше на <ahref="https://docs.pydantic.dev/latest/concepts/pydantic_settings/#dotenv-env-support"class="external-link"target="_blank">Pydantic Settings: Dotenv (.env) support</a>.
Pydantic поддерживает считывание из таких файлов с использованием внешней библиотеки. Вы можете узнать больше на <ahref="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`.
Для этого необходимо выполнить`pip install python-dotenv`.
///
@ -213,7 +213,7 @@ APP_NAME="ChimichangApp"
### Чтение настроек из `.env`
А затем обновите ваш `config.py` с:
А затем обновить ваш `config.py` следующим образом:
//// tab | Pydantic v2
@ -221,7 +221,7 @@ APP_NAME="ChimichangApp"
/// tip | Совет
Атрибут `model_config` используется только для конфигурации Pydantic. Вы можете прочитать больше на <ahref="https://docs.pydantic.dev/latest/concepts/config/"class="external-link"target="_blank">Pydantic: Concepts: Configuration</a>.
Атрибут `model_config` используется только для настройки Pydantic. Вы можете прочитать больше на <ahref="https://docs.pydantic.dev/latest/concepts/config/"class="external-link"target="_blank">Pydantic: Концепции: Конфигурация</a>.
///
@ -233,7 +233,7 @@ APP_NAME="ChimichangApp"
/// tip | Совет
Класс `Config` используется только для конфигурации Pydantic. Вы можете прочитать больше на <ahref="https://docs.pydantic.dev/1.10/usage/model_config/"class="external-link"target="_blank">Pydantic Model Config</a>.
Класс `Config` используется только для настройки Pydantic. Вы можете прочитать больше на <ahref="https://docs.pydantic.dev/1.10/usage/model_config/"class="external-link"target="_blank">Конфигурация Pydantic модели</a>.
///
@ -241,44 +241,44 @@ APP_NAME="ChimichangApp"
/// info | Информация
В версии Pydantic 1 настройка осуществлялась во внутреннем классе `Config`, в версии Pydantic 2 это делается в атрибуте `model_config`. Этот атрибут принимает `dict`, и чтобы получить автозаполнение и inline ошибки, вы можете импортировать и использовать `SettingsConfigDict` для определения этой`dict`.
В версии Pydantic 1 конфигурация осуществлялась во внутреннем классе `Config`, в версии Pydantic 2 это делается в атрибуте `model_config`. Этот атрибут принимает `dict`, и чтобы получить автозавершение и встроенные ошибки, вы можете импортировать и использовать `SettingsConfigDict` для определения этого`dict`.
///
Здесь мы определяем конфигурацию `env_file` внутри класса Pydantic `Settings` и устанавливаем значение на имя файла с файлом dotenv, который мы хотим использовать.
Здесь мы определяем конфигурацию `env_file` внутри вашего класса Pydantic `Settings` и устанавливаем значение для имени файла с файлом dotenv, который мы хотим использовать.
### Создание `Settings` только один раз с помощью `lru_cache`
### Создание `Settings` только один раз с `lru_cache`
Чтение файла с диска обычно является затратной (медленной) операцией, поэтому вы, вероятно, захотите делать это только один раз, а затем повторно использовать тот же объект настроек, вместо того, чтобы считывать его для каждого запроса.
Чтение файла с диска обычно является дорогой (медленной) операцией, поэтому, вероятно, вы захотите делать это только один раз, а затем использовать тот же объект настроек, вместо того чтобы считывать его для каждого запроса.
Но каждый раз, когда мы делаем:
Но каждый раз, когда мы выполняем:
```Python
Settings()
```
будет создаваться новый объект `Settings`, и при создании он будет снова читать файл `.env`.
новый объект `Settings` будет создан, и при создании он снова считает файл `.env`.
Если бы функция зависимости была следующей:
Если бы функция зависимости выглядела так:
```Python
def get_settings():
return Settings()
```
мы бы создавали этот объект для каждого запроса и читали бы файл `.env` для каждого запроса. ⚠️
мы бы создавали этот объект для каждого запроса, и мы бы считывали файл `.env` для каждого запроса. ⚠️
Но поскольку мы используем декоратор `@lru_cache` сверху, объект `Settings` будет создан только один раз, первый раз когда вызывается. ✔️
Но так как мы используем декоратор `@lru_cache` сверху, объект `Settings` будет создан только один раз, при первом вызове. ✔️
Затем для любых последующих вызовов `get_settings()` в зависимостях для следующих запросов, вместо выполнения внутреннего кода `get_settings()` и создания нового объекта `Settings`, он будет возвращать тот же объект, который был возвращен при первом вызове, снова и снова.
Затем для всех последующих вызовов `get_settings()` в зависимостях для следующих запросов, вместо выполнения внутреннего кода функции `get_settings()` и создания нового объекта `Settings`, будет возвращен тот же объект, который был возвращен при первом вызове, снова и снова.
#### Технические подробности `lru_cache`
#### Технические детали `lru_cache`
`@lru_cache` модифицирует функцию, которую он декорирует, чтобы возвращать то же значение, которое было возвращено в первый раз, вместо того, чтобы вычислять его заново, выполняя код функции каждый раз.
`@lru_cache` модифицирует функцию, которую он декорирует, чтобы возвращать то же самое значение, которое было возвращено в первый раз, вместо того чтобы вычислять его снова, выполняя код функции каждый раз.
Таким образом, функция ниже будет выполняться один раз для каждой комбинации аргументов. А затем значения, возвращенные каждой из этих комбинаций аргументов, будут использоваться снова и снова, когда функция вызывается с точно такой же комбинацией аргументов.
Таким образом, функция ниже будет выполнена один раз для каждой комбинации аргументов. А затем возвращаемые значения для каждой из этих комбинаций аргументов будут использоваться снова и снова, когда функция вызывается с точно такой же комбинацией аргументов.
В случае нашей зависимости `get_settings()`, функция даже не принимает никаких аргументов, поэтому она всегда возвращает одно и то же значение.
В случае нашей зависимости `get_settings()`, функция даже не принимает никаких аргументов, поэтому всегда возвращает одно и то же значение.
Таким образом, она ведет себя почти так, как если бы это была просто глобальная переменная. Но поскольку она использует функцию-зависимость, мы можем легко переопределить ее для тестирования.
Таким образом, она ведет себя почти как глобальная переменная. Но поскольку используется функция зависимости, мы можем легко переопределить ее для тестирования.
`@lru_cache` является частью `functools`, который является частью стандартной библиотеки Python, вы можете прочитать больше об этом в <ahref="https://docs.python.org/3/library/functools.html#functools.lru_cache"class="external-link"target="_blank">документации Python для`@lru_cache`</a>.
`@lru_cache` является частью `functools`, который является частью стандартной библиотеки Python, вы можете прочитать больше об этом в <ahref="https://docs.python.org/3/library/functools.html#functools.lru_cache"class="external-link"target="_blank">документации Python по`@lru_cache`</a>.
## Резюме
Вы можете использовать Pydantic Settings для обработки настроек или конфигураций вашего приложения, с всей мощью моделей Pydantic.
Вы можете использовать Pydantic Settings для управления настройками или конфигурациями вашего приложения, используя все возможности Pydantic-моделей.
* С помощью зависимости вы можете упростить тестирование.
* Вы можете использовать `.env` файлы вместе с ним.
* Использование `@lru_cache` позволяет избежать повторного чтения dotenv файла для каждого запроса, при этом позволяя его переопределение во время тестирования.
* Используя зависимость, вы можете упростить тестирование.
* Вы можете использовать `.env` файлы с ним.
* Использование `@lru_cache` позволяет избежать повторного чтения файла dotenv для каждого запроса, при этом позволяя его переопределять во время тестирования.
Если вам нужно иметь два независимых приложения FastAPI, каждое со своей независимой OpenAPI и собственным интерфейсом документации, вы можете создать основное приложение и "монтировать" одно (или несколько) подпрограмм.
Если вам нужно иметь два независимых FastAPI приложения, каждое с собственным, независимым OpenAPI и собственными интерфейсами документации, вы можете создать основное приложение и "смонтировать" одно (или более) подприложение(я).
## Монтирование приложения **FastAPI**
## Монтирование **FastAPI** приложения
"Монтирование" означает добавление полностью "независимого" приложения в определенный путь, которое будет обрабатывать все запросы, поступающие по этому пути, с _operations path_, объявленными в этом подприложении.
"Монтирование" означает добавление полностью "независимого" приложения в определенный path, которое затем будет обрабатывать все под этим path, с _операциями пути_, объявленными в этом подприложении.
### Приложение верхнего уровня
### Высокоуровневое приложение
Сначала создайте главное приложение верхнего уровня **FastAPI** и его *operations path*:
Сначала создайте основное, высшего уровня, **FastAPI** приложение и его *операции пути*:
Затем откройте документацию для подприложения по адресу <ahref="http://127.0.0.1:8000/subapi/docs"class="external-link"target="_blank">http://127.0.0.1:8000/subapi/docs</a>.
Вы увидите автоматическую документацию API для подприложения, содержащую только его _operations path_, все с правильным префиксом подпути`/subapi`:
Вы увидите автоматическую документацию API для подприложения, включающую только его собственные _операции пути_, все с правильным префиксом под-path`/subapi`:
Если вы попробуете взаимодействовать с любым из этих двух пользовательских интерфейсов, они будут работать корректно, потому что браузер сможет общаться с каждым определенным приложением или подприложением.
Если вы попробуете взаимодействовать с любым из двух пользовательских интерфейсов, они будут работать правильно, потому что браузер сможет обратиться к каждому конкретному приложению или подприложению.
### Технические детали: `root_path`
Когда вы монтируете подприложение, как описано выше, FastAPI позаботится о передаче пути монтирования для подприложения с использованием механизма из спецификации ASGI, называемого`root_path`.
Когда вы монтируете подприложение, как описано выше, FastAPI позаботится о передаче пути монтирования для подприложения, используя механизм из спецификации ASGI, называемый`root_path`.
Таким образом, подприложение будет знать об использовании этого префикса пути для интерфейса документации.
Таким образом, подприложение будет знать, что необходимо использовать этот префикс пути для UI документации.
И подприложение также может иметь свои собственные смонтированные подприложения и все будет работать корректно, так как FastAPI автоматически обрабатывает все эти `root_path`.
И подприложение также может иметь свои собственные смонтированные подприложения, и всё будет работать корректно, потому что FastAPI обрабатывает все эти `root_path`s автоматически.
Вы узнаете больше о `root_path` и о том, как использовать его явно, в разделе о [работе за прокси](behind-a-proxy.md){.internal-link target=_blank}.
Вы узнаете больше о `root_path` и том, как его использовать явно в разделе про [Работу за прокси-сервером](behind-a-proxy.md){.internal-link target=_blank}.
Вы можете использовать любой механизм шаблонов с **FastAPI**.
Вы можете использовать любой шаблонизатор, который захотите, с **FastAPI**.
Распространенным выбором является Jinja2, тот же, что используется в Flask и других инструментах.
Популярным выбором является Jinja2, который также используется Flask и другими инструментами.
Есть утилиты для легкой конфигурации, которые вы можете использовать непосредственно в вашем приложении **FastAPI** (предоставлено Starlette).
Существуют утилиты для его простой настройки, которые вы можете использовать непосредственно в своем приложении **FastAPI** (предоставлено Starlette).
## Установка зависимостей
Убедитесь, что вы создаете [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активируйте его и установите`jinja2`:
Убедитесь, что вы создали [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активировали его и установили`jinja2`:
<divclass="termy">
@ -23,43 +23,43 @@ $ pip install jinja2
## Использование `Jinja2Templates`
* Импортируйте `Jinja2Templates`.
* Создайте объект `templates`, который можно будет использовать позже.
* Объявите параметр `Request` в *операции маршрута*, который будет возвращать шаблон.
* Используйте созданные вами `templates`, чтобы рендерить и возвращать `TemplateResponse`, передавая имя шаблона, объект запроса и "контекст" — словарь с парами ключ-значение, которые будут использоваться внутри шаблона Jinja2.
* Создайте объект `templates`, который вы сможете повторно использовать позднее.
* Объявите параметр `Request` в *операции пути*, которая будет возвращать шаблон.
* Используйте созданные вами `templates` для рендеринга и возврата `TemplateResponse`, передайте имя шаблона, объект запроса и словарь "context" с парами "ключ-значение", которые будут использоваться внутри шаблона Jinja2.
До версии FastAPI 0.108.0 и Starlette 0.29.0 первым параметром был `name`.
До FastAPI 0.108.0, Starlette 0.29.0, параметр `name` был первым.
Также до этого, в предыдущих версиях, объект `request` передавался как часть пар ключ-значение в контексте для Jinja2.
Также, до этого, в предыдущих версиях объект `request` передавался как часть пар ключ-значение в контексте для Jinja2.
///
/// tip | Совет
Объявляя `response_class=HTMLResponse`, интерфейс документов сможет знать, что ответ будет в формате HTML.
Объявляя `response_class=HTMLResponse`, интерфейс документации сможет определить, что ответ будет в формате HTML.
///
/// note | Технические подробности
/// note | Технические детали
Вы также можете использовать `from starlette.templating import Jinja2Templates`.
**FastAPI** предоставляет ту же `starlette.templating` под именем`fastapi.templating` просто для вашего удобства, разработчика. Но большинство доступных ответов напрямую из Starlette. То же самое с`Request` и `StaticFiles`.
**FastAPI** предоставляет тот же `starlette.templating` как`fastapi.templating` просто для вашего удобства, разработчик. Но большинство доступных ответов поступают непосредственно от Starlette. То же самое касается`Request` и `StaticFiles`.
///
## Написание шаблонов
Затем вы можете написать шаблон в `templates/item.html` с, например:
Затем вы можете написать шаблон в `templates/item.html`, например:
```jinja hl_lines="7"
{!../../docs_src/templates/templates/item.html!}
```
### Контекстные значения шаблона
### Значения контекста шаблона
В HTML, который содержит:
@ -71,13 +71,13 @@ Item ID: {{ id }}
{% endraw %}
...это отобразит `id`, взятый из "контекста" `dict`, который вы передали:
...будет показан `id`, взятый из "context" `dict`, который вы передали:
```Python
{"id": id}
```
Например, с ID `42`, это отобразится как:
Например, с `id`, равным `42`, это будет рендериться как:
```html
Item ID: 42
@ -85,9 +85,9 @@ Item ID: 42
### Аргументы `url_for` в шаблоне
Вы также можете использовать `url_for()` внутри шаблона, он принимает в качестве аргументов те же аргументы, которые будут использованы вашей *функцией операции маршрута*.
Вы также можете использовать `url_for()` внутри шаблона; он принимает в качестве аргументов те же аргументы, которые использовались бы в вашей *функции-обработчике пути*.
Таким образом, секция с:
Таким образом, раздел с:
{% raw %}
@ -97,23 +97,23 @@ Item ID: 42
{% endraw %}
...создаст ссылку на тот же URL, который будет обрабатываться *функцией операции маршрута*`read_item(id=id)`.
...сгенерирует ссылку на тот же URL, который был бы обработан *функцией-обработчиком пути*`read_item(id=id)`.
Например, с ID `42`, это отобразится как:
Например, с `id`, равным `42`, это будет рендериться как:
```html
<ahref="/items/42">
```
## Шаблоны и статичные файлы
## Шаблоны и статические файлы
Вы также можете использовать `url_for()` внутри шаблона, и использовать его, например, с `StaticFiles`, которые вы закрепили с `name="static"`.
Вы также можете использовать `url_for()` внутри шаблона и использовать его, например, с `StaticFiles`, которые вы смонтировали с `name="static"`.
```jinja hl_lines="4"
{!../../docs_src/templates/templates/item.html!}
```
В этом примере это бы связало с файлом CSS в`static/styles.css` с:
В этом примере это будет ссылка на CSS-файл по пути`static/styles.css` с:
```CSS hl_lines="4"
{!../../docs_src/templates/static/styles.css!}
@ -121,6 +121,6 @@ Item ID: 42
И поскольку вы используете `StaticFiles`, этот CSS-файл будет автоматически обслуживаться вашим приложением **FastAPI** по URL `/static/styles.css`.
## Подробнее
## Дополнительные сведения
Для получения более подробной информации, включая тестирование шаблонов, посетите <ahref="https://www.starlette.io/templates/"class="external-link"target="_blank">документацию Starlette по шаблонам</a>.
Для получения дополнительной информации, включая тестирование шаблонов, смотрите <ahref="https://www.starlette.io/templates/"class="external-link"target="_blank">документацию Starlette по шаблонам</a>.
# Тестирование зависимостей с использованием переопределений
## Переопределение зависимостей во время тестирования
## Переопределение зависимостей в процессе тестирования
Существуют сценарии, когда вы можете захотеть переопределить зависимость во время тестирования.
Существуют сценарии, когда вам может понадобиться переопределить зависимость во время тестирования.
Вы не хотите, чтобы изначальная зависимость запускалась (а также любые ее подзависимости).
Вы не хотите, чтобы оригинальная зависимость выполнялась (также как и любые её подзависимости).
Вместо этого вы хотите предоставить другую зависимость, которая будет использоваться только во время тестов (возможно, только для некоторых конкретных тестов) и будет предоставлять значение, которое можно использовать там, где использовалось значение первоначальной зависимости.
Вместо этого, вы хотите предоставить другую зависимость, которая будет использоваться только во время тестов (возможно, только для некоторых конкретных тестов), и будет предоставлять значение, которое можно использовать там, где использовалось значение оригинальной зависимости.
### Примеры использования: внешний сервис
Примером может быть наличие внешнего поставщика аутентификации, которого вам нужно вызывать.
Примером может служить ситуация, когда у вас есть внешний провайдер аутентификации, которого вам необходимо вызывать.
Вы отправляете ему токен, и он возвращает аутентифицированного пользователя.
Этот поставщик может взимать плату за каждый запрос, и его вызов может занимать больше времени, чем если бы вы использовали заранее заданного пользователя-симулятора для тестов.
Этот провайдер может брать плату за каждый запрос, и вызов его может занять больше времени, чем если бы вы использовали фиксированного пользователя-мока для тестов.
Вы, вероятно, захотите протестировать внешнего поставщика один раз, но не обязательно вызывать его для каждого выполняемого теста.
Вероятно, вы захотите протестировать внешнего провайдера один раз, но не обязательно вызывать его для каждого теста, который вы запускаете.
В этом случае вы можете переопределить зависимость, которая вызывает этого поставщика, и использовать пользовательскую зависимость, которая возвращает пользователя-симулятора, только для ваших тестов.
В этом случае вы можете переопределить зависимость, которая вызывает этого провайдера, и использовать пользовательскую зависимость, которая возвращает мока-пользователя, только для ваших тестов.
### Использование атрибута `app.dependency_overrides`
Для таких случаев ваше приложение **FastAPI** имеет атрибут `app.dependency_overrides`, который представляет собой простой`dict`.
В таких случаях ваше приложение **FastAPI** имеет атрибут `app.dependency_overrides`, который является простым`dict`.
Чтобы переопределить зависимость для тестирования, вы используете в качестве ключа оригинальную зависимость (функцию), а в качестве значения — функцию-переопределение вашей зависимости.
Чтобы переопределить зависимость для тестирования, вы указываете оригинальную зависимость (функцию) в качестве ключа, и ваше переопределение зависимости (другую функцию) в качестве значения.
Затем **FastAPI**вызовет это переопределение вместо оригинальной зависимости.
Затем **FastAPI**будет вызывать это переопределение вместо оригинальной зависимости.
Вы можете задать переопределение зависимости для зависимости, используемой где угодно в вашем приложении**FastAPI**.
Вы можете установить переопределение зависимости для зависимости, используемой в любом месте вашего приложения**FastAPI**.
Изначальная зависимость может использоваться в *функции операции пути*, *декораторе операции пути* (когда вы не используете возвращаемое значение), вызове `.include_router()` и т. д.
Оригинальная зависимость может использоваться в *функции-обработчике пути*, *декораторе операции пути* (если вы не используете возвращаемое значение), вызове `.include_router()`, и т. д.
FastAPI все равно сможет переопределить ее.
FastAPI все равно сможет ее переопределить.
///
Затем вы можете сбросить свои переопределения (удалить их), установив `app.dependency_overrides` в пустой `dict`:
Затем вы можете сбросить ваши переопределения (удалить их), установив `app.dependency_overrides` в пустой `dict`:
```Python
app.dependency_overrides = {}
@ -48,6 +48,6 @@ app.dependency_overrides = {}
/// tip | Совет
Если вы хотите переопределить зависимость только во время некоторых тестов, вы можете установить переопределение в начале теста (внутри функции теста) и сбросить его в конце (в конце функции теста).
Если вы хотите переопределить зависимость только в некоторых тестах, вы можете установить переопределение в начале теста (внутри тестовой функции) и сбросить его в конце (в конце тестовой функции).
Когда вам нужно, чтобы ваши обработчики событий (`startup` и `shutdown`) запускались в тестах, вы можете использовать `TestClient` вместе с оператором `with`:
Когда вам нужно, чтобы ваши обработчики событий (`startup` и `shutdown`) запускались в ваших тестах, вы можете использовать `TestClient` с оператором `with`:
Для получения более подробной информации ознакомьтесь с документацией Starlette по<ahref="https://www.starlette.io/testclient/#testing-websocket-sessions"class="external-link"target="_blank">тестированию WebSockets</a>.
Для получения дополнительных сведений обратитесь к документации Starlette для<ahref="https://www.starlette.io/testclient/#testing-websocket-sessions"class="external-link"target="_blank">тестирования WebSockets</a>.
До сих пор вы объявляли части запроса, которые вам нужны, с указанием их типов.
До сих пор вы объявляли части HTTP-запроса, которые вам нужны, с указанием их типов.
Получение данных из:
* Параметров пути.
* Заголовков.
* Куки.
* path как параметров.
* HTTP-заголовков.
* cookies.
* и т.д.
И при этом **FastAPI** проверяет эти данные, конвертирует их и автоматически генерирует документацию для вашего API.
И благодаря этому, **FastAPI** валидирует эти данные, выполняет их преобразование и автоматически генерирует документацию для вашего API.
Но бывают ситуации, когда может понадобиться доступ к объекту `Request` напрямую.
Но есть ситуации, когда может понадобиться доступ к объекту `Request` напрямую.
## Подробности об объекте `Request`
Так как **FastAPI** на самом деле является **Starlette** под капотом, с рядом различных инструментов поверх, вы можете использовать объект <ahref="https://www.starlette.io/requests/"class="external-link"target="_blank">`Request`</a>от Starlette напрямую, когда это необходимо.
Так как **FastAPI** на самом деле построен на **Starlette**, с уровнем дополнительных инструментов сверху, вы можете использовать объект <ahref="https://www.starlette.io/requests/"class="external-link"target="_blank">`Request`</a>из Starlette напрямую, когда это необходимо.
Это также означает, что если вы получаете данные из объекта `Request` напрямую (например, читаете тело запроса), они не будут проверены, конвертированы или задокументированы (с помощью OpenAPI, для автоматического интерфейса пользователя API) FastAPI.
Это также означает, что если вы получаете данные из объекта `Request` напрямую (например, читаете тело запроса), они не будут валидироваться, преобразовываться или документироваться (с использованием OpenAPI для автоматического пользовательского интерфейса API) в FastAPI.
Хотя любой другой параметр, объявленный обычным образом (например, тело с моделью Pydantic), будет проверяться, конвертироваться, аннотироваться и так далее.
Хотя любой другой параметр, объявленный обычно (например, тело запроса с Pydantic моделью), все равно будет валидироваться, преобразовываться, аннотироваться и т.д.
Но есть конкретные случаи, когда полезно получить объект `Request`.
Но есть определенные случаи, когда полезно получить объект `Request`.
## Используйте объект`Request` напрямую
## Использование объекта`Request` напрямую
Представьте, что вы хотите получить IP-адрес/хост клиента внутри вашей *функции операции пути*.
Представьте, что вы хотите получить IP-адрес/хост клиента внутри вашей *функции-обработчика пути*.
Для этого вам нужно получить доступ к запросу напрямую.
Для этого вам нужно будет получить доступ к запросу напрямую.
Объявив параметр *функции операции пути* с типом `Request`, **FastAPI** поймет, что нужно передать`Request` в этот параметр.
Объявляя параметр *функции-обработчика пути* с типом `Request`, **FastAPI** поймет, что необходимо передать объект`Request` в этот параметр.
/// tip | Совет
/// tip | Подсказка
Обратите внимание, что в этом случае мы объявляем параметр пути рядом с параметром запроса.
Обратите внимание, что в этом случае мы объявляем path параметр вместе с параметром запроса.
Таким образом, параметр пути будет извлечен, проверен, преобразован в указанный тип и аннотирован с OpenAPI.
Таким образом, path параметр будет извлечен, провалидирован, преобразован в указанный тип и аннотирован с помощью OpenAPI.
Аналогично, вы можете объявить любой другой параметр обычным образом, и дополнительно получить объект `Request` тоже.
Точно так же вы можете объявить любой другой параметр как обычно, и дополнительно получить также объект `Request`.
///
## Документация `Request`
## Документация по `Request`
Вы можете прочитать больше подробностей об<ahref="https://www.starlette.io/requests/"class="external-link"target="_blank">объекте `Request` на официальном сайте документации Starlette</a>.
Вы можете прочитать больше деталей о<ahref="https://www.starlette.io/requests/"class="external-link"target="_blank">объекте `Request` на официальном сайте документации Starlette</a>.
/// note | Технические подробности
/// note | Технические Подробности
Вы также можете использовать `from starlette.requests import Request`.
**FastAPI** предоставляет это напрямую просто как удобство для вас, разработчика. Но оно поступает напрямую от Starlette.
**FastAPI** предоставляет это напрямую просто для удобства для вас, разработчика. Но эта функция приходит прямо из Starlette.
Вы можете монтировать WSGI-приложения, как вы видели в [Sub Applications - Mounts](sub-applications.md){.internal-link target=_blank}, [Behind a Proxy](behind-a-proxy.md){.internal-link target=_blank}.
Вы можете монтировать WSGI-приложения, как вы видели в разделе [Дополнительные приложения - Монтирования](sub-applications.md){.internal-link target=_blank}, [За прокси](behind-a-proxy.md){.internal-link target=_blank}.
Для этого вы можете использовать `WSGIMiddleware` и использовать его для обёртки вашего WSGI-приложения, например, Flask, Django и т. д.
Для этого вы можете использовать `WSGIMiddleware`, чтобы обернуть ваше WSGI-приложение, например, Flask, Django и т.д.
## Использование `WSGIMiddleware`
Вам нужно импортировать `WSGIMiddleware`.
Затем оберните WSGI (например, Flask) приложение с помощью middleware.
Затем обернуть WSGI (например, Flask) приложение с помощью middleware (промежуточного слоя).
Теперь каждый запрос по пути `/v1/` будет обрабатываться приложением Flask.
Теперь каждый HTTP-запрос по path `/v1/` будет обработан приложением Flask.
А остальные будут обрабатываться**FastAPI**.
А остальные запросы будут обработаны**FastAPI**.
Если вы запустите его и перейдёте на<ahref="http://localhost:8000/v1/"class="external-link"target="_blank">http://localhost:8000/v1/</a>, вы увидите ответ от Flask:
Если вы запустите его и перейдете по ссылке<ahref="http://localhost:8000/v1/"class="external-link"target="_blank">http://localhost:8000/v1/</a>, вы увидите ответ от Flask:
```txt
Hello, World from Flask!
```
А если вы перейдёте на<ahref="http://localhost:8000/v2"class="external-link"target="_blank">http://localhost:8000/v2</a>, вы увидите ответ от FastAPI:
И если вы перейдете по ссылке<ahref="http://localhost:8000/v2"class="external-link"target="_blank">http://localhost:8000/v2</a>, вы увидите ответ от FastAPI:
Вы можете использовать практически **любого облачного провайдера** для развертывания вашего приложения на FastAPI.
Вы можете использовать практически **любого облачного провайдера** для развертывания вашего приложения FastAPI.
В большинстве случаев у основных облачных провайдеров есть руководства по развертыванию FastAPI с их помощью.
@ -8,9 +8,9 @@
Некоторые облачные провайдеры ✨ [**спонсируют FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, это обеспечивает продолжительное и здоровое **развитие** FastAPI и его **экосистемы**.
И это демонстрирует их искреннюю приверженность FastAPI и его **сообществу** (вам), так как они не только хотят предоставить вам **хороший сервис**, но и убедиться, что у вас есть **хорошая и здоровая платформа**, FastAPI. 🙇
Это демонстрирует их настоящую приверженность FastAPI и его **сообществу** (вам), так как они не только хотят предоставить вам **хороший сервис**, но и хотят убедиться, что у вас есть **хороший и здоровый фреймворк**, FastAPI. 🙇
Возможно, вы захотите попробовать их услуги и следовать их руководствам:
Возможно, вам захочется попробовать их услуги и следовать их руководствам:
До этого момента, с помощью всех руководств в документации, вы, вероятно, запускали **серверную программу**, например, используя команду `fastapi`, которая запускает Uvicorn, выполняющую **один процесс**.
До этого момента, с помощью всех туториалов в документации, вы, вероятно, запускали **серверную программу**, например, используя команду `fastapi`, которая запускает Uvicorn, выполняя **единственный процесс**.
При развертывании приложений, вероятно, вы захотите иметь**репликацию процессов**, чтобы воспользоваться преимуществами **множества ядер** и быть способными обрабатывать больше запросов.
При деплое приложений вы, вероятно, захотите иметь некоторую**репликацию процессов**, чтобы воспользоваться преимуществами **многих ядер** и иметь возможность обрабатывать больше запросов.
Как вы узнали в предыдущей главе о [Концепции Развёртывания](concepts.md){.internal-link target=_blank}, существует множество стратегий, которые можно использовать.
Как вы видели в предыдущей главе о [Концепции деплоя](concepts.md){.internal-link target=_blank}, существует множество стратегий, которые вы можете использовать.
Здесь я покажу вам, как использовать **Uvicorn** с **рабочими процессами**, используя команду `fastapi` или напрямую команду `uvicorn`.
Здесь я покажу вам, как использовать **Uvicorn** с **воркер-процессами**, используя команду `fastapi` или непосредственно команду `uvicorn`.
/// info | Информация
Если вы используете контейнеры, например с Docker или Kubernetes, я расскажу вам больше об этом в следующей главе: [FastAPI в Контейнерах - Docker](docker.md){.internal-link target=_blank}.
Если вы используете контейнеры, например, с Docker или Kubernetes, я расскажу вам больше об этом в следующей главе: [FastAPI в контейнерах - Docker](docker.md){.internal-link target=_blank}.
Особенно при запуске на **Kubernetes** вы, вероятно, **не захотите** использовать рабочих процессы и вместо этого запускать **один процесс Uvicorn на контейнер**, но я расскажу вам о этом позже в той главе.
В частности, работая с **Kubernetes**, вы, вероятно, **не** захотите использовать воркеры, а вместо этого запускать **один процесс Uvicorn в контейнере**, но об этом я расскажу позже в той главе.
///
## Несколько Рабочих Процессов
## Несколько воркеров
Вы можете запустить несколько рабочих процессов с помощью опции командной строки `--workers`:
Вы можете запустить несколько воркеров, используя опцию командной строки `--workers`:
//// tab | `fastapi`
@ -38,41 +38,39 @@
```console
$ <fontcolor="#4E9A06">fastapi</font> run --workers 4 <ustyle="text-decoration-style:solid">main.py</u>
<spanstyle="background-color:#007166"><fontcolor="#D3D7CF"> code </font></span> Импорт объекта приложения FastAPI из модуля с
следующим кодом:
<spanstyle="background-color:#007166"><fontcolor="#D3D7CF"> code </font></span> Импортирование объекта приложения FastAPI из модуля со следующим кодом:
<spanstyle="background-color:#007166"><fontcolor="#D3D7CF"> app </font></span> Используется строка импорта: <fontcolor="#3465A4">main:app</font>
<spanstyle="background-color:#007166"><fontcolor="#D3D7CF"> app </font></span> Использование строки импорта: <fontcolor="#3465A4">main:app</font>
<spanstyle="background-color:#007166"><fontcolor="#D3D7CF"> server </font></span> Сервер запущен на <fontcolor="#729FCF"><ustyle="text-decoration-style:solid">http://0.0.0.0:8000</u></font>
<spanstyle="background-color:#007166"><fontcolor="#D3D7CF"> server </font></span> Документация на<fontcolor="#729FCF"><ustyle="text-decoration-style:solid">http://0.0.0.0:8000/docs</u></font>
<spanstyle="background-color:#007166"><fontcolor="#D3D7CF"> server </font></span> Сервер стартовал на <fontcolor="#729FCF"><ustyle="text-decoration-style:solid">http://0.0.0.0:8000</u></font>
<spanstyle="background-color:#007166"><fontcolor="#D3D7CF"> server </font></span> Документация по адресу<fontcolor="#729FCF"><ustyle="text-decoration-style:solid">http://0.0.0.0:8000/docs</u></font>
Логи:
<spanstyle="background-color:#007166"><fontcolor="#D3D7CF"> INFO </font></span> Uvicorn работает на <fontcolor="#729FCF"><ustyle="text-decoration-style:solid">http://0.0.0.0:8000</u></font><b>(</b>Нажмите CTRL+C для
выхода<b>)</b>
<spanstyle="background-color:#007166"><fontcolor="#D3D7CF"> INFO </font></span> Запущен родительский процесс <b>[</b><fontcolor="#34E2E2"><b>27365</b></font><b>]</b>
<spanstyle="background-color:#007166"><fontcolor="#D3D7CF"> INFO </font></span> Запущен процесс сервера <b>[</b><fontcolor="#34E2E2"><b>27368</b></font><b>]</b>
<spanstyle="background-color:#007166"><fontcolor="#D3D7CF"> INFO </font></span> Запущен процесс сервера <b>[</b><fontcolor="#34E2E2"><b>27369</b></font><b>]</b>
<spanstyle="background-color:#007166"><fontcolor="#D3D7CF"> INFO </font></span> Запущен процесс сервера <b>[</b><fontcolor="#34E2E2"><b>27370</b></font><b>]</b>
<spanstyle="background-color:#007166"><fontcolor="#D3D7CF"> INFO </font></span> Запущен процесс сервера <b>[</b><fontcolor="#34E2E2"><b>27367</b></font><b>]</b>
<spanstyle="background-color:#007166"><fontcolor="#D3D7CF"> INFO </font></span> Uvicorn работает на <fontcolor="#729FCF"><ustyle="text-decoration-style:solid">http://0.0.0.0:8000</u></font><b>(</b>Нажмите CTRL+C для выхода<b>)</b>
<spanstyle="background-color:#007166"><fontcolor="#D3D7CF"> INFO </font></span> Стартовал родительский процесс <b>[</b><fontcolor="#34E2E2"><b>27365</b></font><b>]</b>
<spanstyle="background-color:#007166"><fontcolor="#D3D7CF"> INFO </font></span> Стартовал серверный процесс <b>[</b><fontcolor="#34E2E2"><b>27368</b></font><b>]</b>
<spanstyle="background-color:#007166"><fontcolor="#D3D7CF"> INFO </font></span> Стартовал серверный процесс <b>[</b><fontcolor="#34E2E2"><b>27369</b></font><b>]</b>
<spanstyle="background-color:#007166"><fontcolor="#D3D7CF"> INFO </font></span> Стартовал серверный процесс <b>[</b><fontcolor="#34E2E2"><b>27370</b></font><b>]</b>
<spanstyle="background-color:#007166"><fontcolor="#D3D7CF"> INFO </font></span> Стартовал серверный процесс <b>[</b><fontcolor="#34E2E2"><b>27367</b></font><b>]</b>
<spanstyle="background-color:#007166"><fontcolor="#D3D7CF"> INFO </font></span> Ожидание запуска приложения.
<spanstyle="background-color:#007166"><fontcolor="#D3D7CF"> INFO </font></span> Ожидание запуска приложения.
<spanstyle="background-color:#007166"><fontcolor="#D3D7CF"> INFO </font></span> Ожидание запуска приложения.
<spanstyle="background-color:#007166"><fontcolor="#D3D7CF"> INFO </font></span> Ожидание запуска приложения.
<spanstyle="background-color:#007166"><fontcolor="#D3D7CF"> INFO </font></span> Запуск приложения завершен.
<spanstyle="background-color:#007166"><fontcolor="#D3D7CF"> INFO </font></span> Запуск приложения завершен.
<spanstyle="background-color:#007166"><fontcolor="#D3D7CF"> INFO </font></span> Запуск приложения завершен.
<spanstyle="background-color:#007166"><fontcolor="#D3D7CF"> INFO </font></span> Запуск приложения завершен.
<spanstyle="background-color:#007166"><fontcolor="#D3D7CF"> INFO </font></span> Запуск приложения завершён.
<spanstyle="background-color:#007166"><fontcolor="#D3D7CF"> INFO </font></span> Запуск приложения завершён.
<spanstyle="background-color:#007166"><fontcolor="#D3D7CF"> INFO </font></span> Запуск приложения завершён.
<spanstyle="background-color:#007166"><fontcolor="#D3D7CF"> INFO </font></span> Запуск приложения завершён.
Единственный новый параметр здесь - это `--workers`, который говорит Uvicorn запустить 4 рабочих процесса.
Единственная новая опция здесь - `--workers`, указывающая Uvicorn запустить 4 воркер-процесса.
Вы также можете видеть, что он показывает **PID** каждого процесса,`27365` для родительского процесса (это **менеджер процессов**) и один для каждого рабочего процесса: `27368`, `27369`, `27370` и `27367`.
Вы также можете увидеть, что отображается **PID** каждого процесса:`27365` для родительского процесса (это **менеджер процессов**) и один для каждого воркер-процесса: `27368`, `27369`, `27370` и `27367`.
## Концепции Развёртывания
## Концепции деплоя
Здесь вы увидели, как использовать несколько **рабочих процессов** для **параллелизации** выполнения приложения, использования преимуществ **нескольких ядер** процессора и возможности обслуживать **больше запросов**.
Здесь вы увидели, как использовать несколько **воркеров** для **параллелизации** выполнения приложения, использовать преимущества **многих ядер** процессора и иметь возможность обрабатывать **больше запросов**.
Из списка концепций развёртывания, используя рабочие процессы, вы в основном поможете с частью **репликации** и немного с **перезапусками**, но вы все равно должны позаботиться о других:
Из списка концепций деплоя, приведённого выше, использование воркеров в основном поможет с **репликацией**, и немного - с **перезапусками**, но вам по-прежнему необходимо позаботиться о следующих вещах:
* **Безопасность - HTTPS**
* **Запуск при старте**
* ***Перезапуски***
* Репликация (количество запущенных процессов)
* Репликация (количество рабочих процессов)
* **Память**
* **Предыдущие шаги перед запуском**
* **Подготовительные шаги перед запуском**
## Контейнеры и Docker
В следующей главе о [FastAPI в Контейнерах - Docker](docker.md){.internal-link target=_blank} я объясню некоторые стратегии, которые вы могли бы использовать для обработки других **концепций развёртывания**.
В следующей главе о [FastAPI в контейнерах - Docker](docker.md){.internal-link target=_blank} я объясню некоторые стратегии, которые вы можете использовать для работы с другими **концепциями деплоя**.
Я покажу вам, как **создать свое собственное изображение с нуля**, чтобы запустить единый Uvicorn процесс. Это простой процесс и, вероятно, то, что вы захотите сделать, когда используете распределенную систему управления контейнерами, такую как **Kubernetes**.
Я покажу вам, как **создать собственный образ с нуля** для запуска одного процесса Uvicorn. Это простой процесс и, вероятно, то, что вы захотите сделать при использовании системы управления распределёнными контейнерами, такой как **Kubernetes**.
## Итоги
## Итог
Вы можете использовать несколько рабочих процессов с помощью опции CLI `--workers` с командами `fastapi` или `uvicorn`, чтобы воспользоваться преимуществами **многоядерных процессоров**, для параллельного выполнения **нескольких процессов**.
Вы можете использовать несколько воркер-процессов с опцией `--workers` в CLI с командами `fastapi` или `uvicorn`, чтобы воспользоваться преимуществами **многоядерных процессоров**, для выполнения **нескольких процессов параллельно**.
Вы могли бы использовать эти инструменты и идеи, если вы настраиваете **свою собственную систему развёртывания**, при этом самостоятельно заботясь о других концепциях развёртывания.
Вы можете использовать эти инструменты и идеи, если настраиваете **собственную систему деплоя**, одновременно заботясь о других концепциях деплоя самостоятельно.
Ознакомьтесь с следующей главой, чтобы узнать о **FastAPI** с контейнерами (например, Docker и Kubernetes). Вы увидите, что у этих инструментов есть простые способы решения других **концепций развёртывания**. ✨
Изучите следующую главу, чтобы узнать о **FastAPI** с контейнерами (например, Docker и Kubernetes). Вы увидите, что эти инструменты также имеют простые способы решения других **концепций деплоя**. ✨
Если это необходимо, вы можете использовать настройки и переменные окружения для условной конфигурации OpenAPI в зависимости от среды и даже полностью его отключить.
Если потребуется, вы можете использовать настройки и переменные окружения, чтобы условно настраивать OpenAPI в зависимости от окружения, и даже полностью отключать его.
## О безопасности, API и документации
Сокрытие пользовательских интерфейсов вашей документации в продакшене *не должно* быть способом защиты вашего API.
Скрытие пользовательских интерфейсов вашей документации в продакшне *не должно* быть способом защиты вашего API.
Это не добавляет никакой дополнительной безопасности вашему API, *операции с путями* по-прежнему будут доступны там, где они находятся.
Это не добавляет никакой дополнительной безопасности вашему API, *операции пути* по-прежнему будут доступны там, где они есть.
Если в вашем коде есть уязвимость, она все равно будет существовать.
Если в вашем коде есть уязвимость, она всё равно будет существовать.
Сокрытие документации просто усложняет понимание того, как взаимодействовать с вашим API, и может усложнить отладку в продакшене. Это можно считать просто формой<ahref="https://en.wikipedia.org/wiki/Security_through_obscurity"class="external-link"target="_blank">безопасности через неясность</a>.
Скрытие документации лишь затрудняет понимание того, как взаимодействовать с вашим API, и может усложнить его отладку в продакшне. Это может рассматриваться просто как форма<ahref="https://en.wikipedia.org/wiki/Security_through_obscurity"class="external-link"target="_blank">безопасности через неясность</a>.
Если вы хотите обезопасить ваш API, есть несколько более эффективных методов, например:
Если вы хотите защитить ваш API, есть несколько более эффективных подходов:
* Убедитесь, что у вас есть правильно определенные Pydantic модели для тел запросов и ответов.
* Настройте необходимые разрешения и роли с помощью зависимостей.
* Никогда не храните пароли в открытом виде, только хэши паролей.
* Реализуйте и используйте хорошо известные криптографические инструменты, такие как Passlib и JWT токены и т. д.
* Добавьте более гранулированное управление разрешениями с помощью OAuth2 скоупов там, где это необходимо.
* Убедитесь, что у вас есть хорошо определённые Pydantic-модели для тел запросов и ответов.
* Настройте необходимые разрешения и роли с использованием зависимостей.
* Никогда не храните пароли в открытом виде, только хеши паролей.
* Реализуйте и используйте известные криптографические инструменты, такие как Passlib и JWT токены и т. д.
* Добавьте более детальный контроль разрешений с помощью OAuth2 областей, где это необходимо.
* ...и так далее.
Тем не менее, у вас может быть очень специфичный случай, когда вам действительно нужно отключить документацию API для какой-то среды (например, для продакшена) или в зависимости от конфигураций из переменных окружения.
Тем не менее, может быть очень специфический случай, когда вам действительно нужно отключить документацию API для какого-то окружения (например, для продакшна) или в зависимости от настроек из переменных окружения.
## Условный OpenAPI из настроек и переменных окружения
Вы можете легко использовать те же настройки Pydantic для конфигурации вашего сгенерированного OpenAPI и пользовательских интерфейсов документации.
Вы можете легко использовать те же настройки Pydantic для конфигурации вашего сгенерированного OpenAPI и интерфейсов документации.
Например:
@ -33,9 +33,9 @@
Здесь мы объявляем настройку `openapi_url` с тем же значением по умолчанию `"/openapi.json"`.
Затем мы используем её при создании приложения `FastAPI`.
Затем мы используем его при создании приложения `FastAPI`.
Затем вы можете отключить OpenAPI (включая UI документацию), установив переменную окружения `OPENAPI_URL` в пустую строку, например:
Затем вы можете отключить OpenAPI (включая документацию UI) путем установки значения переменной окружения `OPENAPI_URL` в пустую строку, так:
<divclass="termy">
@ -47,7 +47,7 @@ $ OPENAPI_URL= uvicorn main:app
</div>
Затем, если вы перейдете по URL на`/openapi.json`, `/docs` или `/redoc`, вы получите ошибку `404 Not Found`, например:
Затем, если вы перейдете по URL-адресам`/openapi.json`, `/docs` или `/redoc`, вы получите ошибку `404 Not Found`, например:
Таким же образом вы можете задать тему подсветки синтаксиса с помощью ключа `"syntaxHighlight.theme"` (обратите внимание, что в нём есть точка посередине):
Таким же образом вы можете установить тему подсветки синтаксиса с помощью ключа `"syntaxHighlight.theme"` (обратите внимание, что в нем есть точка):
@ -50,13 +50,13 @@ FastAPI включает некоторые параметры конфигур
## Другие параметры Swagger UI
Чтобы увидеть все возможные конфигурации, которые вы можете использовать, прочтите официальную <ahref="https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/"class="external-link"target="_blank">документацию о параметрах Swagger UI</a>.
Чтобы узнать о всех возможных конфигурациях, которые вы можете использовать, прочитайте официальную <ahref="https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/"class="external-link"target="_blank">документацию по параметрам Swagger UI</a>.
## Настройки только для JavaScript
Swagger UI также позволяет настроить другие параметры как объекты**только для JavaScript** (например, функции JavaScript).
Swagger UI также позволяет использовать другие конфигурации, представляющие собой**только для JavaScript** объекты (например, функции JavaScript).
FastAPI также включает эти настройки `presets` только для JavaScript:
FastAPI также включает эти настройки `presets`, предназначенные только для JavaScript:
```JavaScript
presets: [
@ -65,6 +65,6 @@ presets: [
]
```
Это объекты **JavaScript**, а не строки, поэтому вы не можете передавать их напрямую из Python-кода.
Это объекты **JavaScript**, а не строки, поэтому вы не можете передавать их напрямую из кода Python.
Если вам нужно использовать конфигурации только для JavaScript, как эти, вы можете использовать один из методов, упомянутых выше. Переопределите всю *path operation* Swagger UI и вручную напишите любой необходимый JavaScript.
Если вам нужно использовать такие настройки, предназначенные только для JavaScript, вы можете использовать один из методов выше. Переопределите все *операции пути* Swagger UI и вручную напишите любой нужный JavaScript.
# Статические ресурсы пользовательского интерфейса для документации (самостоятельный хостинг)
API документация использует **Swagger UI** и **ReDoc**, и каждому из них требуются некоторые файлы JavaScript и CSS.
Документация API использует **Swagger UI** и **ReDoc**, и для каждой из них нужны определенные JavaScript и CSS файлы.
По умолчанию эти файлы предоставляются через<abbrtitle="Сеть доставки контента: служба, обычно состоящая из нескольких серверов, которая предоставляет статические файлы, такие как JavaScript и CSS. Обычно используется для доставки этих файлов с сервера, наиболее близкого к клиенту, что улучшает производительность.">CDN</abbr>.
По умолчанию эти файлы обслуживаются с<abbrtitle="Content Delivery Network: сервис, обычно состоящий из нескольких серверов, который предоставляет статические файлы, такие как JavaScript и CSS. Обычно используется для обслуживания этих файлов с сервера, находящегося ближе к клиенту, улучшая производительность.">CDN</abbr>.
Но возможно его настроить: можно установить определенный CDN или разместить файлы самостоятельно.
Но можно настроить их по-своему, задать определенный CDN или обслуживать файлы самостоятельно.
## Пользовательский CDN для JavaScript и CSS
Предположим, что вы хотите использовать другой <abbrtitle="Сеть доставки контента">CDN</abbr>, например, хотите использовать`https://unpkg.com/`.
Допустим, вы хотите использовать другой <abbrtitle="Content Delivery Network">CDN</abbr>, например, `https://unpkg.com/`.
Это может быть полезно, если, например, вы живете в стране, где некоторые URL-адреса ограничены.
Это может быть полезно, если, например, вы живете в стране, где ограничивают некоторые URL-адреса.
### Отключение автоматической документации
### Отключите автоматическую документацию
Первый шаг — отключить автоматическую документацию, так как по умолчанию они используют стандартный CDN.
Первый шаг — отключить автоматическую документацию, так как по умолчанию она использует стандартный CDN.
Чтобы отключить их, установите их URL в`None` при создании вашего приложения `FastAPI`:
Чтобы отключить их, установите для их URL-адресов значение`None` при создании приложения `FastAPI`:
Теперь вы можете создать *path operations* для пользовательской документации.
Теперь вы можете создать *операции пути* для пользовательской документации.
Вы можете повторно использовать внутренние функции FastAPI для создания HTML-страниц документации и передавать им необходимые аргументы:
Вы можете повторно использовать внутренние функции FastAPI для создания HTML-страниц для документации и передать им необходимые аргументы:
* `openapi_url`: URL-адрес, по которому HTML-страница документации может получить схему OpenAPI вашего API. Здесь можно использовать атрибут `app.openapi_url`.
* `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.
* `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...
@ -38,33 +38,33 @@ API документация использует **Swagger UI** и **ReDoc**,
/// tip | Совет
*Path operation* для `swagger_ui_redirect` является помощником, когда вы используете OAuth2.
*Операция пути* для `swagger_ui_redirect` — это вспомогательная функция, когда вы используете OAuth2.
Если вы интегрируете свой API с провайдером OAuth2, вы сможете авторизоваться и вернуться к документации API с полученными учетными данными. И взаимодействовать с ним, используя реальную аутентификацию через OAuth2.
Если вы интегрируете свой API с провайдером OAuth2, вы сможете пройти аутентификацию и вернуться к документации API с полученными учетными данными. И взаимодействовать с ней, используя настоящую аутентификацию OAuth2.
Swagger UI сделает это для вас за кулисами, но для этого нужен этот помощник "перенаправления".
Swagger UI справится с этим закулисно для вас, но ему нужен этот вспомогательный "редирект".
///
### Создание *path operation* для его тестирования
### Создайте *операцию пути* для тестирования
Теперь, чтобы проверить, что все работает, создайте *path operation*:
Теперь, чтобы убедиться, что всё работает, создайте *операцию пути*:
Теперь вы должны иметь возможность перейти на свою документацию по адресу <ahref="http://127.0.0.1:8000/docs"class="external-link"target="_blank">http://127.0.0.1:8000/docs</a> и перезагрузить страницу, она загрузит эти ресурсы с нового CDN.
Теперь вы должны иметь возможность перейти к вашей документации по адресу <ahref="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 может быть полезен, если, например, вам нужно, чтобы ваше приложение продолжало работать даже в автономном режиме, без доступа в интернет или в локальной сети.
Самостоятельный хостинг JavaScript и CSS может быть полезен, если, например, вам нужно, чтобы ваше приложение продолжало работать даже в офлайн-режиме, без открытого доступа к Интернету, или в локальной сети.
Здесь вы увидите, как самостоятельно размещать эти файлы в том же приложении FastAPI и настраивать документацию для их использования.
Здесь вы увидите, как можно обслуживать эти файлы самостоятельно, в том же приложении FastAPI, и настроить документацию для их использования.
### Структура файлов проекта
Допустим, ваша структура файлов проекта выглядит следующим образом:
Допустим, структура файлов вашего проекта выглядит так:
```
.
@ -75,7 +75,7 @@ Swagger UI сделает это для вас за кулисами, но дл
Теперь создайте каталог для хранения этих статических файлов.
Ваша новая структура файлов может выглядеть так:
Теперь ваша новая структура файлов может выглядеть так:
```
.
@ -85,9 +85,9 @@ Swagger UI сделает это для вас за кулисами, но дл
└── static/
```
### Загрузка файлов
### Загрузите файлы
Скачайте необходимые для документации статические файлы и поместите их в каталог`static/`.
Загрузите необходимые статические файлы для документации и разместите их в каталоге`static/`.
Вы можете щелкнуть правой кнопкой мыши по каждой ссылке и выбрать опцию, аналогичную `Сохранить ссылку как...`.
@ -100,7 +100,7 @@ Swagger UI сделает это для вас за кулисами, но дл
Запустите приложение и перейдите по адресу<ahref="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>.
Запустите ваше приложение и перейдите на<ahref="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
/*! For license information please see redoc.standalone.js.LICENSE.txt */
/*! Для информации о лицензии смотрите redoc.standalone.js.LICENSE.txt */
Это подтверждает, что вы можете обслуживать статические файлы из вашего приложения и что вы разместили статические файлы для документации в правильном месте.
Это подтверждает, что вы можете обслуживать статические файлы из вашего приложения, и что вы разместили статические файлы для документации в правильном месте.
Теперь мы можем настроить приложение для использования этих статических файлов в документации.
Теперь мы можем настроить приложение для использования этих статических файлов для документации.
### Отключение автоматической документации для статических файлов
### Отключите автоматическую документацию для статических файлов
Как и при использовании пользовательского CDN, первым шагом является отключение автоматической документации, так как по умолчанию они используют CDN.
Также как при использовании пользовательского CDN, первый шаг — отключить автоматическую документацию, так как по умолчанию они используют CDN.
Чтобы отключить их, установите их URL в`None` при создании вашего приложения `FastAPI`:
Чтобы отключить их, установите для их URL-адресов значение`None` при создании приложения `FastAPI`:
### Включение пользовательской документации для статических файлов
### Включите пользовательскую документацию для статических файлов
И так же, как с пользовательским CDN, теперь вы можете создать *path operations* для пользовательской документации.
И аналогично предыдущим шагам с пользовательским CDN, теперь вы можете создать *операции пути* для пользовательской документации.
Снова можно использовать внутренние функции FastAPI для создания HTML-страниц документации и передавать им необходимые аргументы:
Опять же, вы можете повторно использовать внутренние функции FastAPI для создания HTML-страниц для документации и передать им необходимые аргументы:
* `openapi_url`: URL-адрес, по которому HTML-страница документации может получить схему OpenAPI вашего API. Здесь можно использовать атрибут `app.openapi_url`.
* `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**. **Это файл, который ваше приложение теперь обслуживает**.
* `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...
@ -164,22 +164,22 @@ Swagger UI сделает это для вас за кулисами, но дл
/// tip | Совет
*Path operation* для `swagger_ui_redirect` является помощником, когда вы используете OAuth2.
*Операция пути* для `swagger_ui_redirect` — это вспомогательная функция, когда вы используете OAuth2.
Если вы интегрируете свой API с провайдером OAuth2, вы сможете авторизоваться и вернуться к документации API с полученными учетными данными. И взаимодействовать с ним, используя реальную аутентификацию через OAuth2.
Если вы интегрируете свой API с провайдером OAuth2, вы сможете пройти аутентификацию и вернуться к документации API с полученными учетными данными. И взаимодействовать с ней, используя настоящую аутентификацию OAuth2.
Swagger UI сделает это для вас за кулисами, но для этого нужен этот помощник "перенаправления".
Swagger UI справится с этим закулисно для вас, но ему нужен этот вспомогательный "редирект".
///
### Создание *path operation* для тестирования статических файлов
### Создайте *операцию пути* для тестирования статических файлов
Теперь, чтобы убедиться, что все работает, создайте *path operation*:
Теперь, чтобы убедиться, что всё работает, создайте *операцию пути*:
Теперь вы должны иметь возможность отключить Wi-Fi, перейти на свою документацию по адресу <ahref="http://127.0.0.1:8000/docs"class="external-link"target="_blank">http://127.0.0.1:8000/docs</a> и перезагрузить страницу.
Теперь вы должны иметь возможность отключить WiFi, перейти к вашей документации по адресу <ahref="http://127.0.0.1:8000/docs"class="external-link"target="_blank">http://127.0.0.1:8000/docs</a> и перезагрузить страницу.
И даже без Интернета вы сможете увидеть документацию вашего API и взаимодействовать с ней.
И даже без Интернета вы сможете увидеть документацию для вашего API и взаимодействовать с ней.
В некоторых случаях вы можете захотеть переопределить логику, используемую классами`Request` и `APIRoute`.
В некоторых случаях вам может понадобиться переопределить логику, используемую в классах`Request` и `APIRoute`.
В частности, это может быть хорошей альтернативой логике в middleware.
В частности, это может быть хорошей альтернативой логике в middleware (Промежуточный слой).
Например, если вы хотите прочитать или изменить тело запроса до его обработки вашим приложением.
Например, если вы хотите прочитать или изменить тело запроса до того, как оно будет обработано вашим приложением.
/// danger | Опасность
/// danger
Это "продвинутая" функция.
Если вы только начинаете с **FastAPI**, возможно, вам стоит пропустить этот раздел.
Если вы только начинаете работать с **FastAPI**, вы можете пропустить этот раздел.
///
## Примеры использования
## Сценарии использования
Некоторые примеры использования включают:
Некоторые сценарии использования включают:
* Конвертацию тел запросов, которые не являются JSON, в JSON (например, <ahref="https://msgpack.org/index.html"class="external-link"target="_blank">`msgpack`</a>).
* Распаковку тел запросов, сжатых с помощью gzip.
* Конвертация тел запросов, не являющихся JSON, в JSON (например, <ahref="https://msgpack.org/index.html"class="external-link"target="_blank">`msgpack`</a>).
* Разжатие тел запросов, сжатых методом gzip.
* Автоматическое логирование всех тел запросов.
## Обработка кастомных кодировок тел запросов
## Обработка пользовательских кодировок тела запроса
Давайте рассмотрим, как использовать подкласс `Request` для распаковки запросов в формате gzip.
Давайте посмотрим, как использовать пользовательский подкласс `Request` для разжатия gzip-запросов.
И подкласс `APIRoute` для использования этого кастомного класса запроса.
И подкласс `APIRoute`, чтобы использовать этот пользовательский класс запроса.
### Создание кастомного класса `GzipRequest`
### Создание пользовательского класса `GzipRequest`
/// tip | Подсказка
/// tip | Совет
Это учебный пример, демонстрирующий, как это работает; если вам нужна поддержка Gzip, вы можете использовать предоставленный [`GzipMiddleware`](../advanced/middleware.md#gzipmiddleware){.internal-link target=_blank}.
Это учебный пример, чтобы показать как это работает. Если вам нужна поддержка gzip, вы можете использовать предоставленный [`GzipMiddleware`](../advanced/middleware.md#gzipmiddleware){.internal-link target=_blank}.
///
Сначала мы создаем класс `GzipRequest`, который будет переопределять метод `Request.body()` для распаковки тела при наличии соответствующего заголовка.
Сначала мы создадим класс `GzipRequest`, который переопределит метод `Request.body()`, чтобы разжать тело запроса, если присутствует соответствующий заголовок.
Если в заголовке нет `gzip`, он не будет пытаться распаковать тело.
Если в заголовке нет `gzip`, он не будет пытаться разжать тело запроса.
Таким образом, один и тот же класс маршрута может обрабатывать как сжатые, так и несжатые запросы в формате gzip.
Таким образом, один и тот же класс маршрута может обрабатывать как gzip-сжатые, так и несжатые запросы.
`Request` имеет атрибут `request.scope`, это просто Python `dict`, содержащий метаданные, связанные с запросом.
`Request` имеет атрибут `request.scope`, который является просто Python-словарем, содержащим метаданные, относящиеся к запросу.
`Request` также имеет `request.receive`, это функция для "получения" тела запроса.
`scope``dict`и функция `receive` обе являются частью спецификации ASGI.
Словарь `scope` и функция `receive` являются частью спецификации ASGI.
И эти две вещи, `scope` и `receive`, — то, что нужно для создания нового экземпляра `Request`.
И именно эти две вещи, `scope` и `receive`, нужны для создания нового экземпляра `Request`.
Чтобы узнать больше о `Request`, ознакомьтесь с <ahref="https://www.starlette.io/requests/"class="external-link"target="_blank">документацией Starlette о запросах</a>.
///
Единственное, что функция, возвращаемая `GzipRequest.get_route_handler`, делает иначе — это преобразует `Request` в `GzipRequest`.
Единственное, что функция, возвращаемая `GzipRequest.get_route_handler`, делает иначе, это преобразует `Request` в `GzipRequest`.
Делая это, наш `GzipRequest` позаботится о распаковке данных (если это необходимо) до передачи их нашим *операциям на пути*.
Таким образом, наш `GzipRequest` позаботится о разжатии данных (если это необходимо) перед передачей их нашим *операциям пути*.
После этого вся логика обработки остается той же.
После этого вся логика обработки такая же.
Но из-за наших изменений в `GzipRequest.body`, тело запроса будет автоматически распаковано, когда оно понадобится **FastAPI**.
Но благодаря изменениям в `GzipRequest.body`, тело запроса будет автоматически разжиматься, когда **FastAPI** нужно будет его загрузить.
## Доступ к телу запроса в обработчике исключений
/// tip | Подсказка
Для решения этой же проблемы, вероятно, гораздо проще использовать `body` в кастомном обработчике для `RequestValidationError` ([Обработка ошибок](../tutorial/handling-errors.md#use-the-requestvalidationerror-body){.internal-link target=_blank}).
Чтобы решить эту же проблему, вероятно, гораздо проще использовать `body` в пользовательском обработчике для `RequestValidationError` ([Обработка ошибок](../tutorial/handling-errors.md#use-the-requestvalidationerror-body){.internal-link target=_blank}).
Но этот пример все же действителен и показывает, как взаимодействовать с внутренними компонентами.
Но этот пример по-прежнему актуален и показывает, как взаимодействовать с внутренними компонентами.
///
Мы также можем использовать этот же подход для доступа к телу запроса в обработчике исключений.
Мы также можем использовать этот же подход, чтобы получить доступ к телу запроса в обработчике исключений.
Все, что нам нужно сделать, это обработать запрос внутри блока `try`/`except`:
Все, что нам нужно сделать, — это обработать запрос внутри блока `try`/`except`:
Если произойдет исключение, экземпляр `Request` все равно будет в доступной области, так что мы сможем прочитать и использовать тело запроса при обработке ошибки:
Если возникает исключение, экземпляр `Request` все равно будет доступен, поэтому мы можем прочитать и использовать тело запроса при обработке ошибки:
В этом примере операции пути под `router` будут использовать кастомный класс `TimedRoute` и добавят в ответ заголовок `X-Response-Time` с временем, затраченным на генерацию ответа:
В этом примере *операции путей* под `router` будут использовать пользовательский класс `TimedRoute`, и в ответе будет добавлен дополнительный HTTP-заголовок `X-Response-Time` с временем, затраченным на генерацию ответа:
Есть случаи, когда вам может понадобиться изменить сгенерированную схему OpenAPI.
Бывают случаи, когда вам может понадобиться изменить сгенерированную схему OpenAPI.
В этом разделе вы увидите, как это сделать.
В этом разделе вы узнаете, как это сделать.
## Обычный процесс
Обычный (по умолчанию) процесс таков:
Обычный (по умолчанию) процесс выглядит следующим образом.
`FastAPI` приложение (экземпляр) имеет метод `.openapi()`, который должен возвращать схему OpenAPI.
Приложение `FastAPI` (экземпляр) имеет метод `.openapi()`, который ожидается вернуть схему OpenAPI.
В процессе создания объекта приложения регистрируется *операция пути*для `/openapi.json` (или для того, что вы установили в `openapi_url`).
В рамках создания объекта приложения для `/openapi.json` (или для того, что вы указали в `openapi_url`) регистрируется *операция пути*.
Он просто возвращает JSON-ответ с результатом выполнения метода `.openapi()` приложения.
Она просто возвращает JSON ответ с результатом метода `.openapi()` приложения.
По умолчанию, метод `.openapi()` проверяет свойство `.openapi_schema`, чтобы увидеть, есть ли в нем содержимое, и возвращает его.
По умолчанию метод `.openapi()` проверяет свойство `.openapi_schema`, чтобы увидеть, есть ли в нем содержимое, и возвращает их.
Если его нет, он генерирует его с использованием вспомогательной функции из`fastapi.openapi.utils.get_openapi`.
Если его нет, он генерирует его с использованием вспомогательной функции в`fastapi.openapi.utils.get_openapi`.
Эта функция `get_openapi()` принимает в качестве параметров:
И эта функция `get_openapi()` принимает в качестве параметров:
* `title`: Заголовок OpenAPI, показываемый в документации.
* `version`: Версия вашего API, например,`2.5.0`.
* `openapi_version`: Версия спецификации OpenAPI, используемая по умолчанию. На данный момент последняя версия: `3.1.0`.
* `title`: Заголовок OpenAPI, отображаемый в документации.
* `version`: Версия вашего API, например `2.5.0`.
* `openapi_version`: Версия спецификации OpenAPI, используемой. По умолчанию последняя: `3.1.0`.
* `summary`: Краткое резюме API.
* `description`: Описание вашего API, которое может включать markdown и будет показано в документации.
* `routes`: Список маршрутов, каждое из них является зарегистрированной *операцией пути*. Они взяты из `app.routes`.
* `description`: Описание вашего API, оно может включать markdown и будет показано в документации.
* `routes`: Список маршрутов, это каждая из зарегистрированных *операций пути*. Они берутся из `app.routes`.
/// info | Информация
Параметр `summary` доступен в OpenAPI 3.1.0 и выше, поддерживается в FastAPI 0.99.0 и выше.
Параметр `summary` доступен в OpenAPI 3.1.0 и выше, поддерживается FastAPI 0.99.0 и выше.
///
## Переопределение стандартных значений
## Переопределение значений по умолчанию
Используя вышеуказанную информацию, вы можете использовать ту же вспомогательную функцию для генерации схемы OpenAPI и переопределить каждую часть, которая вам нужна.
Используя информацию выше, вы можете использовать ту же вспомогательную функцию для генерации схемы OpenAPI и переопределения каждой части, которая вам нужна.
Например, давайте добавим <ahref="https://github.com/Rebilly/ReDoc/blob/master/docs/redoc-vendor-extensions.md#x-logo"class="external-link"target="_blank">расширение OpenAPI от ReDoc, чтобы включить нестандартный логотип</a>.
Например, давайте добавим <ahref="https://github.com/Rebilly/ReDoc/blob/master/docs/redoc-vendor-extensions.md#x-logo"class="external-link"target="_blank">OpenAPI расширение ReDoc для включения настраиваемого логотипа</a>.
### Обычный **FastAPI**
Сначала напишите все ваше приложение **FastAPI** как обычно:
Сначала напишите все ваше приложение на **FastAPI** как обычно:
Как только вы перейдете на<ahref="http://127.0.0.1:8000/redoc"class="external-link"target="_blank">http://127.0.0.1:8000/redoc</a>, вы увидите, что используете свой нестандартный логотип (в этом примере, логотип **FastAPI**):
Как только вы перейдете по адресу<ahref="http://127.0.0.1:8000/redoc"class="external-link"target="_blank">http://127.0.0.1:8000/redoc</a>, вы увидите, что вы используете ваш настраиваемый логотип (в этом примере используется логотип **FastAPI**):
Вот несколько указателей на другие места в документации для общих или частых вопросов.
Вот несколько путеводителей на другие части документации для общих или часто задаваемых вопросов.
## Фильтрация данных - Безопасность
Чтобы убедиться, что вы не возвращаете больше данных, чем следует, прочитайте документацию [Учебник - Модель ответа - Возвращаемый тип](../tutorial/response-model.md){.internal-link target=_blank}.
Чтобы убедиться, что вы не возвращаете больше данных, чем следует, прочитайте документацию по [Учебнику - Модель ответа - Возвращаемый тип](../tutorial/response-model.md){.internal-link target=_blank}.
## Теги документации - OpenAPI
Чтобы добавить теги к вашим *операциям путей* и сгруппировать их в интерфейсе документации, прочитайте документацию [Учебник - Конфигурация операций пути - Теги](../tutorial/path-operation-configuration.md#tags){.internal-link target=_blank}.
Чтобы добавить теги к вашим *операциям пути* и сгруппировать их в интерфейсе документации, прочитайте документацию по [Учебнику - Конфигурации операций пути - Теги](../tutorial/path-operation-configuration.md#tags){.internal-link target=_blank}.
## Обзор и описание документации - OpenAPI
## Краткое описание и описание документации - OpenAPI
Чтобы добавить обзор и описание к вашим *операциям путей* и показать их в интерфейсе документации, прочитайте документацию [Учебник - Конфигурация операций пути - Обзор и описание](../tutorial/path-operation-configuration.md#summary-and-description){.internal-link target=_blank}.
Чтобы добавить краткое описание и описание к вашим *операциям пути* и отобразить их в интерфейсе документации, прочитайте документацию по [Учебнику - Конфигурации операций пути - Краткое описание и описание](../tutorial/path-operation-configuration.md#summary-and-description){.internal-link target=_blank}.
## Описание ответа в документации - OpenAPI
Чтобы определить описание ответа, показанное в интерфейсе документации, прочитайте документацию [Учебник - Конфигурация операций пути - Описание ответа](../tutorial/path-operation-configuration.md#response-description){.internal-link target=_blank}.
Чтобы определить описание ответа, отображаемого в интерфейсе документации, прочитайте документацию по [Учебнику - Конфигурации операций пути - Описание ответа](../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}.
Чтобы пометить *операцию пути* как устаревшую и отобразить это в интерфейсе документации, прочитайте документацию по [Учебнику - Конфигурации операций пути - Устаревание](../tutorial/path-operation-configuration.md#deprecate-a-path-operation){.internal-link target=_blank}.
## Конвертация любых данных в совместимые с JSON
## Преобразование любых данных в совместимые с JSON
Чтобы конвертировать любые данные в совместимые с JSON, прочитайте документацию [Учебник - Кодировщик, совместимый с JSON](../tutorial/encoder.md){.internal-link target=_blank}.
Чтобы преобразовать любые данные в совместимые с JSON, прочитайте документацию по [Учебнику - Совместимый кодировщик JSON](../tutorial/encoder.md){.internal-link target=_blank}.
## OpenAPI Метаданные - Документация
## Метаданные OpenAPI - Документация
Чтобы добавить метаданные к вашей OpenAPI схеме, включая лицензию, версию, контактные данные и т.д., прочитайте документацию [Учебник - Метаданные и URL-адреса документации](../tutorial/metadata.md){.internal-link target=_blank}.
Чтобы добавить метаданные в вашу схему OpenAPI, включая лицензию, версию, контактную информацию и т.д., прочитайте документацию по [Учебнику - Метаданные и URL-адреса документации](../tutorial/metadata.md){.internal-link target=_blank}.
## Настройка URL-адреса OpenAPI
## Пользовательский URL OpenAPI
Чтобы настроить (или удалить) URL-адрес OpenAPI, прочитайте документацию [Учебник - Метаданные и URL-адреса документации](../tutorial/metadata.md#openapi-url){.internal-link target=_blank}.
Чтобы кастомизировать (или удалить) 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}.
Чтобы обновить URL-адреса, используемые для автоматически сгенерированных интерфейсов документации, прочитайте документацию по [Учебнику - Метаданные и URL-адреса документации](../tutorial/metadata.md#docs-urls){.internal-link target=_blank}.
* С <ahref="https://tartiflette.github.io/tartiflette-asgi/"class="external-link"target="_blank">Tartiflette ASGI</a> для интеграции с ASGI
* С <ahref="https://tartiflette.github.io/tartiflette-asgi/"class="external-link"target="_blank">Tartiflette ASGI</a> для обеспечения интеграции с ASGI
* С <ahref="https://github.com/ciscorn/starlette-graphene3"class="external-link"target="_blank">starlette-graphene3</a>
## GraphQL с Strawberry
## GraphQL с использованием Strawberry
Если вам нужно или вы хотите работать с **GraphQL**, <ahref="https://strawberry.rocks/"class="external-link"target="_blank">**Strawberry**</a>— это **рекомендуемая** библиотека, так как она имеет дизайн, близкий к дизайну **FastAPI**, и основана на **аннотациях типов**.
Если вам нужно или вы хотите работать с **GraphQL**, библиотека <ahref="https://strawberry.rocks/"class="external-link"target="_blank">**Strawberry**</a>является **рекомендуемой**, так как ее дизайн наиболее близок к дизайну **FastAPI**, и всё основано на **аннотациях типов**.
В зависимости от вашего случая использования, вы можете предпочесть другую библиотеку, но если спросите меня, я скорее всего предложу вам попробовать **Strawberry**.
В зависимости от вашего случая использования, вы можете предпочесть другую библиотеку, но если бы вы спросили меня, я бы, вероятно, предложил попробовать **Strawberry**.
Вот небольшой пример того, как вы можете интегрировать Strawberry с FastAPI:
Вот небольшой пример того, как можно интегрировать Strawberry с FastAPI:
Вы можете узнать больше о Strawberry в <ahref="https://strawberry.rocks/"class="external-link"target="_blank">документации Strawberry</a>.
Также обратите внимание на документацию о <ahref="https://strawberry.rocks/docs/integrations/fastapi"class="external-link"target="_blank">Strawberry с FastAPI</a>.
А также документацию о <ahref="https://strawberry.rocks/docs/integrations/fastapi"class="external-link"target="_blank">Strawberry с FastAPI</a>.
## Более старая `GraphQLApp` из Starlette
## Устаревший `GraphQLApp` от Starlette
Предыдущие версии Starlette включали класс `GraphQLApp` для интеграции с <ahref="https://graphene-python.org/"class="external-link"target="_blank">Graphene</a>.
В предыдущих версиях Starlette включалась класс `GraphQLApp` для интеграции с <ahref="https://graphene-python.org/"class="external-link"target="_blank">Graphene</a>.
Он был исключен из Starlette, но если у вас есть код, который его использует, вы можете легко **мигрировать** на <ahref="https://github.com/ciscorn/starlette-graphene3"class="external-link"target="_blank">starlette-graphene3</a>, которая охватывает тот же случай использования и имеет **почти идентичный интерфейс**.
Он был исключен из Starlette, но если у вас есть код, который использует его, вы можете легко **мигрировать** на <ahref="https://github.com/ciscorn/starlette-graphene3"class="external-link"target="_blank">starlette-graphene3</a>, который покрывает тот же случай использования и имеет **почти идентичный интерфейс**.
/// tip | Совет
Если вам нужен GraphQL, я все же рекомендую вам обратить внимание на <ahref="https://strawberry.rocks/"class="external-link"target="_blank">Strawberry</a>, так как он основан на аннотациях типов, а не на пользовательских классах и типах.
Если вам нужен GraphQL, я всё же рекомендую обратить внимание на <ahref="https://strawberry.rocks/"class="external-link"target="_blank">Strawberry</a>, так как он основан на аннотациях типов, а не на кастомных классах и типах.
///
@ -57,4 +57,4 @@
Вы можете узнать больше о **GraphQL** в <ahref="https://graphql.org/"class="external-link"target="_blank">официальной документации GraphQL</a>.
Вы также можете прочитать больше о каждой из упомянутых выше библиотек по предоставленным ссылкам.
Также вы можете прочитать больше о каждой из описанных выше библиотек по предоставленным ссылкам.
Здесь вы найдете различные рецепты или руководства «как сделать» по **разным темам**.
Здесь вы найдете различные рецепты или руководства "как сделать" для **различных тем**.
Большинство из этих идей будут более или менее **независимыми**, и в большинстве случаев вам нужно изучать их только в случае, если они применимы непосредственно к **вашему проекту**.
Большинство этих идей будут более или менее **независимыми**, и в большинстве случаев вам следует изучать их только в том случае, если они напрямую применимы к **вашему проекту**.
Если что-то кажется интересным и полезным для вашего проекта, смело проверяйте это, но в противном случае вы, вероятно, сможете просто их пропустить.
Если что-то кажется интересным и полезным для вашего проекта, смело изучайте это, в противном случае вы можете просто пропустить их.
/// tip | Совет
Если вы хотите **изучить FastAPI** структурированным образом (рекомендуется), идите и читайте [Учебник - Руководство пользователя](../tutorial/index.md){.internal-link target=_blank} глава за главой.
Если вы хотите **изучить FastAPI** структурированным образом (рекомендуется), прочитайте [Учебник - Руководство пользователя](../tutorial/index.md){.internal-link target=_blank} главу за главой.
# Раздельные схемы OpenAPI для ввода и вывода или нет
# Отдельные схемы OpenAPI для ввода и вывода или нет
При использовании **Pydantic v2** созданный OpenAPI немного более точен и **правилен**, чем раньше. 😎
При использовании **Pydantic v2** сгенерированный OpenAPI более точный и **правильный**, чем раньше. 😎
Фактически, в некоторых случаях, для одной и той же модели Pydantic в OpenAPI даже будет **две схемы JSON** — для ввода и вывода, в зависимости от того, имеют ли они **значения по умолчанию**.
Фактически, в некоторых случаях, будет даже **две схемы JSON** в OpenAPI для одной и той же модели Pydantic: для ввода и вывода, в зависимости от наличия **значений по умолчанию**.
Посмотрим, как это работает, и как изменить это, если вам нужно это сделать.
Давайте посмотрим, как это работает и как изменить это, если необходимо.
## Pydantic модели для ввода и вывода
## Модели Pydantic для ввода и вывода
Предположим, у вас есть модель Pydantic со значениями по умолчанию, например, такая:
Предположим, у вас есть модель Pydantic со значениями по умолчанию, как эта:
...тогда, так как`description` имеет значение по умолчанию, если вы **ничего не возвращаете** для этого поля, оно все равно будет иметь это **значение по умолчанию**.
...тогда, поскольку`description` имеет значение по умолчанию, если вы **ничего не вернете** для этого поля, оно по-прежнему будет иметь это **значение по умолчанию**.
### Модель для данных ответа вывода
### Модель для данных ответа
Если вы взаимодействуете с документацией и проверяете ответ, даже если код ничего не добавил в одно из полей `description`, JSON ответ содержит значение по умолчанию (`null`):
Если вы взаимодействуете с документацией и проверяете ответ, даже если в коде ничего не добавлялось в одно из полей `description`, JSON-ответ содержит значение по умолчанию (`null`):
Это означает, что у него **всегда будет значение**, просто иногда это значение может быть `None` (или `null` в терминах JSON).
Это означает, что у него **всегда будет значение**, просто иногда это значение может быть `None` (или `null` в JSON).
Это означает, что клиенты, использующие ваш API, не должны проверять, существует значение или нет, они могут **предполагать, что поле всегда будет там**, но в некоторых случаях у него будет значение по умолчанию `None`.
Это значит, что клиенты, использующие ваш API, не должны проверять, существует ли значение, они могут **предполагать, что поле всегда будет**, но в некоторых случаях оно будет иметь значение по умолчанию `None`.
Способ описания этого в OpenAPI состоит в том, чтобы пометить это поле как **обязательное**, поскольку оно всегда будет там.
Способ описать это в OpenAPI — отметить это поле как **обязательное**, потому что оно всегда будет присутствовать.
Из-за этого схема JSON для модели может отличаться в зависимости от того, используется ли она для **ввода или вывода**:
Из-за этого, схема JSON для модели может отличаться в зависимости от того, используется она для **ввода или вывода**:
* для **ввода**`description` **не будет обязательным**
* для **вывода** оно будет **обязательным** (и возможно `None`, или в терминах JSON `null`)
* для **вывода** оно будет **обязательным** (и возможно `None`, или в терминах JSON,`null`)
### Модель для вывода в документации
Вы тоже можете проверить модель вывода в документации, **оба** поля `name` и `description`помечены как **обязательные** с **красной звездочкой**:
Вы также можете проверить модель вывода в документации, **оба** поля `name` и `description` отмечены как **обязательные** с **красной звездочкой**:
С этой функцией из **Pydantic v2** ваша документация API становится более **точной**, и если у вас есть автоматически сгенерированные клиенты и SDK, они также будут более точными, с лучшим **опытом разработчика** и согласованностью. 🎉
С этой функцией из **Pydantic v2**, ваша документация API более **точная**, и если у вас есть автоматически сгенерированные клиенты и SDK, они будут более точными тоже, с более лучшим **опытом разработчика** и консистентностью. 🎉
## Не разделяйте схемы
## Не разделять схемы
Теперь, в некоторых случаях вы, возможно, захотите иметь **одну и ту же схему для ввода и вывода**.
Теперь, есть случаи, когда вы можете захотеть иметь **одну и ту же схему для ввода и вывода**.
Возможно, главным случаем использования является то, что если у вас уже есть сгенерированный клиентский код/SDK, и вы не хотите обновлять весь клиентский код/SDK пока, вы, вероятно, захотите сделать это в какой-то момент, но, возможно, не сейчас.
Возможно, основным случаем использования будет, если у вас уже есть какой-то автоматически сгенерированный клиентский код/SDK, и вы не хотите обновлять весь автоматически сгенерированный клиентский код/SDK еще. Вы, вероятно, захотите сделать это в какой-то момент, но, возможно, не прямо сейчас.
В этом случае вы можете отключить эту функцию в **FastAPI** с параметром `separate_input_output_schemas=False`.
/// info | Информация
Поддержка `separate_input_output_schemas` была добавлена в FastAPI `0.102.0`. 🤓
Поддержка `separate_input_output_schemas` была добавлена в FastAPI в версии`0.102.0`. 🤓
Вы можете изучить базы данных, SQL и SQLModel в <ahref="https://sqlmodel.tiangolo.com/"class="external-link"target="_blank">документации SQLModel</a>. 🤓
Есть мини-<ahref="https://sqlmodel.tiangolo.com/tutorial/fastapi/"class="external-link"target="_blank">руководство по использованию SQLModel с FastAPI</a>. ✨
Есть мини<ahref="https://sqlmodel.tiangolo.com/tutorial/fastapi/"class="external-link"target="_blank">учебное пособие по использованию SQLModel с FastAPI</a>. ✨
Это руководство включает раздел о <ahref="https://sqlmodel.tiangolo.com/tutorial/fastapi/tests/"class="external-link"target="_blank">тестировании SQL баз данных</a>. 😎
Этот учебник включает в себя раздел о <ahref="https://sqlmodel.tiangolo.com/tutorial/fastapi/tests/"class="external-link"target="_blank">тестировании SQL баз данных</a>. 😎