🌐 Update Russian translations for existing pages (LLM-generated) (#14123)

* Update Russian translations for modified pages

* docs: fix translation for multiprocessing

* Update Russian translations for other existing pages

* Apply changes from latest PRs: 13917 and 14099

---------

Co-authored-by: vldmrdev <70532790+vldmrdev@users.noreply.github.com>
This commit is contained in:
Motov Yurii
2025-09-30 13:24:39 +02:00
committed by GitHub
parent 3d70b26788
commit 977abe2396
74 changed files with 4157 additions and 3885 deletions

View File

@@ -1,84 +1,84 @@
# Фоновые задачи
# Фоновые задачи { #background-tasks }
Вы можете создавать фоновые задачи, которые будут выполнятся *после* возвращения ответа сервером.
Вы можете создавать фоновые задачи, которые будут выполняться после возврата ответа.
Это может быть полезно для функций, которые должны выполниться после получения запроса, но ожидание их выполнения необязательно для пользователя.
Это полезно для операций, которые должны произойти после HTTP-запроса, но клиенту не обязательно ждать их завершения, чтобы получить ответ.
К примеру:
Например:
* Отправка писем на почту после выполнения каких-либо действий:
* Т.к. соединение с почтовым сервером и отправка письма идут достаточно "долго" (несколько секунд), вы можете отправить ответ пользователю, а отправку письма выполнить в фоне.
* Уведомления по электронной почте, отправляемые после выполнения действия:
* Так как подключение к почтовому серверу и отправка письма обычно «медленные» (несколько секунд), вы можете сразу вернуть ответ, а отправку уведомления выполнить в фоне.
* Обработка данных:
* К примеру, если вы получаете файл, который должен пройти через медленный процесс, вы можете отправить ответ "Accepted" (HTTP 202) и отправить работу с файлом в фон.
* Например, если вы получаете файл, который должен пройти через медленный процесс, вы можете вернуть ответ «Accepted» (HTTP 202) и обработать файл в фоне.
## Использование класса `BackgroundTasks`
## Использование `BackgroundTasks` { #using-backgroundtasks }
Сначала импортируйте `BackgroundTasks`, потом добавьте в функцию параметр с типом `BackgroundTasks`:
Сначала импортируйте `BackgroundTasks` и объявите параметр в вашей функции‑обработчике пути с типом `BackgroundTasks`:
{* ../../docs_src/background_tasks/tutorial001.py hl[1,13] *}
**FastAPI** создаст объект класса `BackgroundTasks` для вас и запишет его в параметр.
**FastAPI** создаст объект типа `BackgroundTasks` для вас и передаст его через этот параметр.
## Создание функции для фоновой задачи
## Создание функции для фоновой задачи { #create-a-task-function }
Создайте функцию, которую хотите запустить в фоне.
Создайте функцию, которую нужно запустить как фоновую задачу.
Это совершенно обычная функция, которая может принимать параметры.
Это обычная функция, которая может принимать параметры.
Она может быть как асинхронной `async def`, так и обычной `def` функцией, **FastAPI** знает, как правильно ее выполнить.
Это может быть как `async def`, так и обычная функция `def`, **FastAPI** знает, как корректно её выполнить.
В нашем примере фоновая задача будет вести запись в файл (симулируя отправку письма).
В этом случае функция задачи будет записывать данные в файл (имитируя отправку письма).
Так как операция записи не использует `async` и `await`, мы определим ее как обычную `def`:
Так как операция записи не использует `async` и `await`, мы определим функцию как обычную `def`:
{* ../../docs_src/background_tasks/tutorial001.py hl[6:9] *}
## Добавление фоновой задачи
## Добавление фоновой задачи { #add-the-background-task }
Внутри функции вызовите метод `.add_task()` у объекта *background tasks* и передайте ему функцию, которую хотите выполнить в фоне:
Внутри вашей функции‑обработчика пути передайте функцию задачи объекту фоновых задач методом `.add_task()`:
{* ../../docs_src/background_tasks/tutorial001.py hl[14] *}
`.add_task()` принимает следующие аргументы:
* Функцию, которая будет выполнена в фоне (`write_notification`). Обратите внимание, что передается объект функции, без скобок.
* Любое упорядоченное количество аргументов, которые принимает функция (`email`).
* Любое количество именованных аргументов, которые принимает функция (`message="some notification"`).
* Функцию задачи, которую нужно выполнить в фоне (`write_notification`).
* Последовательность позиционных аргументов, которые должны быть переданы функции задачи, в порядке (`email`).
* Любые именованные аргументы, которые должны быть переданы функции задачи (`message="some notification"`).
## Встраивание зависимостей
## Встраивание зависимостей { #dependency-injection }
Класс `BackgroundTasks` также работает с системой встраивания зависимостей, вы можете определить `BackgroundTasks` на разных уровнях: как параметр функции, как завимость, как подзависимость и так далее.
Использование `BackgroundTasks` также работает с системой встраивания зависимостей, вы можете объявить параметр типа `BackgroundTasks` на нескольких уровнях: в функции‑обработчике пути, в зависимости (dependable), в подзависимости и т. д.
**FastAPI** знает, что нужно сделать в каждом случае и как переиспользовать тот же объект `BackgroundTasks`, так чтобы все фоновые задачи собрались и запустились вместе в фоне:
**FastAPI** знает, что делать в каждом случае и как переиспользовать один и тот же объект, так чтобы все фоновые задачи были объединены и затем выполнены в фоне:
{* ../../docs_src/background_tasks/tutorial002_py310.py hl[11,13,20,23] *}
{* ../../docs_src/background_tasks/tutorial002_an_py310.py hl[13,15,22,25] *}
В этом примере сообщения будут записаны в `log.txt` *после* того, как ответ сервера был отправлен.
В этом примере сообщения будут записаны в файл `log.txt` после отправки ответа.
Если бы в запрос был передан query-параметр `q`, он бы первыми записался в `log.txt` фоновой задачей (потому что вызывается в зависимости `get_query`).
Если в запросе была строка запроса (query), она будет записана в лог фоновой задачей.
После другая фоновая задача, которая была сгенерирована в функции, запишет сообщение из параметра `email`.
Затем другая фоновая задача, созданная в функции‑обработчике пути, запишет сообщение, используя pathпараметр `email`.
## Технические детали
## Технические детали { #technical-details }
Класс `BackgroundTasks` основан на <a href="https://www.starlette.io/background/" class="external-link" target="_blank">`starlette.background`</a>.
Класс `BackgroundTasks` приходит напрямую из <a href="https://www.starlette.io/background/" class="external-link" target="_blank">`starlette.background`</a>.
Он интегрирован в FastAPI, так что вы можете импортировать его прямо из `fastapi` и избежать случайного импорта `BackgroundTask` (без `s` на конце) из `starlette.background`.
Он импортируется/включается прямо в FastAPI, чтобы вы могли импортировать его из `fastapi` и избежать случайного импорта альтернативного `BackgroundTask` (без `s` на конце) из `starlette.background`.
При использовании `BackgroundTasks` (а не `BackgroundTask`), вам достаточно только определить параметр функции с типом `BackgroundTasks` и **FastAPI** сделает все за вас, также как при использовании объекта `Request`.
Используя только `BackgroundTasks` (а не `BackgroundTask`), его можно применять как параметр функции‑обработчика пути, и **FastAPI** сделает остальное за вас, как при использовании объекта `Request` напрямую.
Вы все равно можете использовать `BackgroundTask` из `starlette` в FastAPI, но вам придется самостоятельно создавать объект фоновой задачи и вручную обработать `Response` внутри него.
По‑прежнему можно использовать один `BackgroundTask` в FastAPI, но тогда вам нужно создать объект в своём коде и вернуть Starlette `Response`, включающий его.
Вы можете подробнее изучить его в <a href="https://www.starlette.io/background/" class="external-link" target="_blank">Официальной документации Starlette для BackgroundTasks</a>.
Подробнее см. в <a href="https://www.starlette.io/background/" class="external-link" target="_blank">официальной документации Starlette по фоновым задачам</a>.
## Предостережение
## Предостережение { #caveat }
Если вам нужно выполнить тяжелые вычисления в фоне, которым необязательно быть запущенными в одном процессе с приложением **FastAPI**примеру, вам не нужны обрабатываемые переменные или вы не хотите делиться памятью процесса и т.д.), вы можете использовать более серьезные инструменты, такие как <a href="https://docs.celeryproject.org" class="external-link" target="_blank">Celery</a>.
Если вам нужно выполнять тяжелые вычисления в фоне, и при этом они не обязательно должны запускаться тем же процессом (например, вам не нужно делиться памятью, переменными и т. п.), вам могут подойти более мощные инструменты, такие как <a href="https://docs.celeryq.dev" class="external-link" target="_blank">Celery</a>.
Их тяжелее настраивать, также им нужен брокер сообщений наподобие RabbitMQ или Redis, но зато они позволяют вам запускать фоновые задачи в нескольких процессах и даже на нескольких серверах.
Они обычно требуют более сложной конфигурации, менеджера очереди сообщений/заданий (например, RabbitMQ или Redis), но позволяют запускать фоновые задачи в нескольких процессах и, что особенно важно, на нескольких серверах.
Но если вам нужен доступ к общим переменным и объектам вашего **FastAPI** приложения или вам нужно выполнять простые фоновые задачи (наподобие отправки письма из примера) вы можете просто использовать `BackgroundTasks`.
Но если вам нужен доступ к переменным и объектам из того же приложения **FastAPI**, или нужно выполнять небольшие фоновые задачи (например, отправку emailуведомления), вы можете просто использовать `BackgroundTasks`.
## Резюме
## Резюме { #recap }
Для создания фоновых задач вам необходимо импортировать `BackgroundTasks` и добавить его в функцию, как параметр с типом `BackgroundTasks`.
Импортируйте и используйте `BackgroundTasks` с параметрами в функциях‑обработчиках пути и зависимостях, чтобы добавлять фоновые задачи.

View File

@@ -1,4 +1,4 @@
# Большие приложения, в которых много файлов
# Большие приложения, в которых много файлов { #bigger-applications-multiple-files }
При построении приложения или веб-API нам редко удается поместить всё в один файл.
@@ -10,7 +10,7 @@
///
## Пример структуры приложения
## Пример структуры приложения { #an-example-file-structure }
Давайте предположим, что наше приложение имеет следующую структуру:
@@ -71,7 +71,7 @@ from app.routers import items
│   └── admin.py # суб-модуль "admin", напр.: import app.internal.admin
```
## `APIRouter`
## `APIRouter` { #apirouter }
Давайте предположим, что для работы с пользователями используется отдельный файл (суб-модуль) `/app/routers/users.py`.
@@ -81,8 +81,7 @@ from app.routers import items
С помощью `APIRouter` вы можете создать *операции пути* (*эндпоинты*) для данного модуля.
### Импорт `APIRouter`
### Импорт `APIRouter` { #import-apirouter }
Точно также, как и в случае с классом `FastAPI`, вам нужно импортировать и создать объект класса `APIRouter`.
@@ -90,7 +89,7 @@ from app.routers import items
{!../../docs_src/bigger_applications/app/routers/users.py!}
```
### Создание *эндпоинтов* с помощью `APIRouter`
### Создание *эндпоинтов* с помощью `APIRouter` { #path-operations-with-apirouter }
В дальнейшем используйте `APIRouter` для объявления *эндпоинтов*, точно также, как вы используете класс `FastAPI`:
@@ -112,7 +111,7 @@ from app.routers import items
Мы собираемся подключить данный `APIRouter` к нашему основному приложению на `FastAPI`, но сначала давайте проверим зависимости и создадим ещё один модуль с `APIRouter`.
## Зависимости
## Зависимости { #dependencies }
Нам понадобятся некоторые зависимости, которые мы будем использовать в разных местах нашего приложения.
@@ -154,11 +153,11 @@ from app.routers import items
Для простоты мы воспользовались неким воображаемым заголовоком.
В реальных случаях для получения наилучших результатов используйте интегрированные утилиты обеспечения безопасности [Security utilities](security/index.md){.internal-link target=_blank}.
В реальных случаях для получения наилучших результатов используйте интегрированные [утилиты безопасности](security/index.md){.internal-link target=_blank}.
///
## Ещё один модуль с `APIRouter`
## Ещё один модуль с `APIRouter` { #another-module-with-apirouter }
Давайте также предположим, что у вас есть *эндпоинты*, отвечающие за обработку "items", и они находятся в модуле `app/routers/items.py`.
@@ -203,7 +202,7 @@ async def read_item(item_id: str):
/// tip | Подсказка
Обратите внимание, что также, как и в случае с зависимостями в декораторах *эндпоинтов* ([dependencies in *path operation decorators*](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), никакого значения в *функцию эндпоинта* передано не будет.
Обратите внимание, что также, как и в случае с зависимостями в декораторах *эндпоинтов* ([зависимости в декораторах операций пути](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), никакого значения в *функцию эндпоинта* передано не будет.
///
@@ -219,8 +218,8 @@ async def read_item(item_id: str):
* Каждый из них будет включать предопределенные ответы `responses`.
* Каждый *эндпоинт* будет иметь список зависимостей (`dependencies`), исполняемых перед вызовом *эндпоинта*.
* Если вы определили зависимости в самой операции пути, **то она также будет выполнена**.
* Сначала выполняются зависимости маршрутизатора, затем вызываются зависимости, определенные в декораторе *эндпоинта* ([`dependencies` in the decorator](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), и, наконец, обычные параметрические зависимости.
* Вы также можете добавить зависимости безопасности с областями видимости (`scopes`) [`Security` dependencies with `scopes`](../advanced/security/oauth2-scopes.md){.internal-link target=_blank}.
* Сначала выполняются зависимости маршрутизатора, затем вызываются [зависимости в декораторе](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, и, наконец, обычные параметрические зависимости.
* Вы также можете добавить [зависимости `Security` с `scopes`](../advanced/security/oauth2-scopes.md){.internal-link target=_blank}.
/// tip | Подсказка
@@ -234,7 +233,7 @@ async def read_item(item_id: str):
///
### Импорт зависимостей
### Импорт зависимостей { #import-the-dependencies }
Наш код находится в модуле `app.routers.items` (файл `app/routers/items.py`).
@@ -246,7 +245,7 @@ async def read_item(item_id: str):
{!../../docs_src/bigger_applications/app/routers/items.py!}
```
#### Как работает относительный импорт?
#### Как работает относительный импорт? { #how-relative-imports-work }
/// tip | Подсказка
@@ -308,7 +307,7 @@ from ...dependencies import get_token_header
Теперь вы знаете, как работает импорт в Python, и сможете использовать относительное импортирование в своих собственных приложениях любого уровня сложности. 🤓
### Добавление пользовательских тегов (`tags`), ответов (`responses`) и зависимостей (`dependencies`)
### Добавление пользовательских тегов (`tags`), ответов (`responses`) и зависимостей (`dependencies`) { #add-some-custom-tags-responses-and-dependencies }
Мы не будем добавлять префикс `/items` и список тегов `tags=["items"]` для каждого *эндпоинта*, т.к. мы уже их добавили с помощью `APIRouter`.
@@ -326,7 +325,7 @@ from ...dependencies import get_token_header
///
## Модуль main в `FastAPI`
## Модуль main в `FastAPI` { #the-main-fastapi }
Теперь давайте посмотрим на модуль `app/main.py`.
@@ -336,17 +335,17 @@ from ...dependencies import get_token_header
И теперь, когда большая часть логики приложения разделена на отдельные модули, основной файл `app/main.py` будет достаточно простым.
### Импорт `FastAPI`
### Импорт `FastAPI` { #import-fastapi }
Вы импортируете и создаете класс `FastAPI` как обычно.
Мы даже можем объявить глобальные зависимости [global dependencies](dependencies/global-dependencies.md){.internal-link target=_blank}, которые будут объединены с зависимостями для каждого отдельного маршрутизатора:
Мы даже можем объявить [глобальные зависимости](dependencies/global-dependencies.md){.internal-link target=_blank}, которые будут объединены с зависимостями для каждого отдельного маршрутизатора:
```Python hl_lines="1 3 7" title="app/main.py"
{!../../docs_src/bigger_applications/app/main.py!}
```
### Импорт `APIRouter`
### Импорт `APIRouter` { #import-the-apirouter }
Теперь мы импортируем другие суб-модули, содержащие `APIRouter`:
@@ -356,7 +355,7 @@ from ...dependencies import get_token_header
Так как файлы `app/routers/users.py` и `app/routers/items.py` являются суб-модулями одного и того же Python-пакета `app`, то мы сможем их импортировать, воспользовавшись операцией относительного импорта `.`.
### Как работает импорт?
### Как работает импорт? { #how-the-importing-works }
Данная строка кода:
@@ -398,7 +397,7 @@ from app.routers import items, users
///
### Избегайте конфликтов имен
### Избегайте конфликтов имен { #avoid-name-collisions }
Вместо того чтобы импортировать только переменную `router`, мы импортируем непосредственно суб-модуль `items`.
@@ -419,7 +418,7 @@ from .routers.users import router
{!../../docs_src/bigger_applications/app/main.py!}
```
### Подключение маршрутизаторов (`APIRouter`) для `users` и для `items`
### Подключение маршрутизаторов (`APIRouter`) для `users` и для `items` { #include-the-apirouters-for-users-and-items }
Давайте подключим маршрутизаторы (`router`) из суб-модулей `users` и `items`:
@@ -457,7 +456,7 @@ from .routers.users import router
///
### Подключение `APIRouter` с пользовательскими префиксом (`prefix`), тегами (`tags`), ответами (`responses`), и зависимостями (`dependencies`)
### Подключение `APIRouter` с пользовательскими префиксом (`prefix`), тегами (`tags`), ответами (`responses`), и зависимостями (`dependencies`) { #include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies }
Теперь давайте представим, что ваша организация передала вам файл `app/internal/admin.py`.
@@ -491,7 +490,7 @@ from .routers.users import router
Например, другие проекты, могут использовать тот же самый `APIRouter` с другими методами аутентификации.
### Подключение отдельного *эндпоинта*
### Подключение отдельного *эндпоинта* { #include-a-path-operation }
Мы также можем добавить *эндпоинт* непосредственно в основное приложение `FastAPI`.
@@ -517,7 +516,7 @@ from .routers.users import router
///
## Проверка автоматической документации API
## Проверка автоматической документации API { #check-the-automatic-api-docs }
Теперь запустите приложение:
@@ -537,7 +536,7 @@ $ fastapi dev app/main.py
<img src="/img/tutorial/bigger-applications/image01.png">
## Подключение существующего маршрута через новый префикс (`prefix`)
## Подключение существующего маршрута через новый префикс (`prefix`) { #include-the-same-router-multiple-times-with-different-prefix }
Вы можете использовать `.include_router()` несколько раз с одним и тем же маршрутом, применив различные префиксы.
@@ -545,7 +544,7 @@ $ fastapi dev app/main.py
Это продвинутый способ, который вам может и не пригодится. Мы приводим его на случай, если вдруг вам это понадобится.
## Включение одного маршрутизатора (`APIRouter`) в другой
## Включение одного маршрутизатора (`APIRouter`) в другой { #include-an-apirouter-in-another }
Точно так же, как вы включаете `APIRouter` в приложение `FastAPI`, вы можете включить `APIRouter` в другой `APIRouter`:

View File

@@ -1,12 +1,12 @@
# Body - Поля
# Body - Поля { #body-fields }
Таким же способом, как вы объявляете дополнительную валидацию и метаданные в параметрах *функции обработки пути* с помощью функций `Query`, `Path` и `Body`, вы можете объявлять валидацию и метаданные внутри Pydantic моделей, используя функцию `Field` из Pydantic.
## Импорт `Field`
## Импорт `Field` { #import-field }
Сначала вы должны импортировать его:
{* ../../docs_src/body_fields/tutorial001_py310.py hl[2] *}
{* ../../docs_src/body_fields/tutorial001_an_py310.py hl[4] *}
/// warning | Внимание
@@ -14,11 +14,11 @@
///
## Объявление атрибутов модели
## Объявление атрибутов модели { #declare-model-attributes }
Вы можете использовать функцию `Field` с атрибутами модели:
{* ../../docs_src/body_fields/tutorial001_py310.py hl[9:12] *}
{* ../../docs_src/body_fields/tutorial001_an_py310.py hl[11:14] *}
Функция `Field` работает так же, как `Query`, `Path` и `Body`, у неё такие же параметры и т.д.
@@ -26,9 +26,9 @@
На самом деле, `Query`, `Path` и другие функции, которые вы увидите в дальнейшем, создают объекты подклассов общего класса `Param`, который сам по себе является подклассом `FieldInfo` из Pydantic.
И `Field` (из Pydantic), и `Body`, оба возвращают объекты подкласса `FieldInfo`.
И `Field` (из Pydantic) также возвращает экземпляр `FieldInfo`.
У класса `Body` есть и другие подклассы, с которыми вы ознакомитесь позже.
`Body` также напрямую возвращает объекты подкласса `FieldInfo`. И есть и другие, с которыми вы познакомитесь позже, которые являются подклассами класса `Body`.
Помните, что когда вы импортируете `Query`, `Path` и другое из `fastapi`, это фактически функции, которые возвращают специальные классы.
@@ -40,12 +40,11 @@
///
## Добавление дополнительной информации
## Добавление дополнительной информации { #add-extra-information }
Вы можете объявлять дополнительную информацию в `Field`, `Query`, `Body` и т.п. Она будет включена в сгенерированную JSON схему.
Вы узнаете больше о добавлении дополнительной информации позже в документации, когда будете изучать, как задавать примеры принимаемых данных.
Вы узнаете больше о добавлении дополнительной информации позже в документации, когда будете изучать, как задавать примеры.
/// warning | Внимание
@@ -54,7 +53,7 @@
///
## Резюме
## Резюме { #recap }
Вы можете использовать функцию `Field` из Pydantic, чтобы задавать дополнительную валидацию и метаданные для атрибутов модели.

View File

@@ -1,8 +1,8 @@
# Body - Множество параметров
# Body - Множество параметров { #body-multiple-parameters }
Теперь, когда мы увидели, как использовать `Path` и `Query` параметры, давайте рассмотрим более продвинутые примеры обьявления тела запроса.
Теперь, когда мы увидели, как использовать `Path` и `Query` параметры, давайте рассмотрим более продвинутые примеры объявления тела запроса.
## Обьединение `Path`, `Query` и параметров тела запроса
## Объединение `Path`, `Query` и параметров тела запроса { #mix-path-query-and-body-parameters }
Во-первых, конечно, вы можете объединять параметры `Path`, `Query` и объявления тела запроса в своих функциях обработки, **FastAPI** автоматически определит, что с ними нужно делать.
@@ -16,9 +16,9 @@
///
## Несколько параметров тела запроса
## Несколько параметров тела запроса { #multiple-body-parameters }
В предыдущем примере, *операции пути* ожидали тело запроса в формате JSON-тело с параметрами, соответствующими атрибутам `Item`, например:
В предыдущем примере, *операции пути* ожидали тело запроса в формате JSON, с параметрами, соответствующими атрибутам `Item`, например:
```JSON
{
@@ -33,7 +33,7 @@
{* ../../docs_src/body_multiple_params/tutorial002_py310.py hl[20] *}
В этом случае **FastAPI** заметит, что в функции есть более одного параметра тела (два параметра, которые являются моделями Pydantic).
В этом случае **FastAPI** заметит, что в функции есть более одного параметра тела (два параметра, которые являются Pydantic-моделями).
Таким образом, имена параметров будут использоваться в качестве ключей (имён полей) в теле запроса, и будет ожидаться запрос следующего формата:
@@ -54,21 +54,21 @@
/// note | Внимание
Обратите внимание, что хотя параметр `item` был объявлен таким же способом, как и раньше, теперь предпологается, что он находится внутри тела с ключом `item`.
Обратите внимание, что хотя параметр `item` был объявлен таким же способом, как и раньше, теперь предполагается, что он находится внутри тела с ключом `item`.
///
**FastAPI** сделает автоматические преобразование из запроса, так что параметр `item` получит своё конкретное содержимое, и то же самое происходит с пользователем `user`.
**FastAPI** сделает автоматическое преобразование из запроса, так что параметр `item` получит своё конкретное содержимое, и то же самое происходит с пользователем `user`.
Произойдёт проверка составных данных, и создание документации в схеме OpenAPI и автоматических документах.
## Отдельные значения в теле запроса
## Отдельные значения в теле запроса { #singular-values-in-body }
Точно так же, как `Query` и `Path` используются для определения дополнительных данных для query и path параметров, **FastAPI** предоставляет аналогичный инструмент - `Body`.
Например, расширяя предыдущую модель, вы можете решить, что вам нужен еще один ключ `importance` в том же теле запроса, помимо параметров `item` и `user`.
Если вы объявите его без указания, какой именно объект (Path, Query, Body и .т.п.) ожидаете, то, поскольку это является простым типом данных, **FastAPI** будет считать, что это query-параметр.
Если вы объявите его без указания, какой именно объект (Path, Query, Body и т.п.) ожидаете, то, поскольку это является простым типом данных, **FastAPI** будет считать, что это query-параметр.
Но вы можете указать **FastAPI** обрабатывать его, как ещё один ключ тела запроса, используя `Body`:
@@ -94,7 +94,7 @@
И всё будет работать так же - преобразование типов данных, валидация, документирование и т.д.
## Множество body и query параметров
## Множество body и query параметров { #multiple-body-params-and-query }
Конечно, вы также можете объявлять query-параметры в любое время, дополнительно к любым body-параметрам.
@@ -112,7 +112,7 @@ q: str | None = None
Например:
{* ../../docs_src/body_multiple_params/tutorial004_an_py310.py hl[27] *}
{* ../../docs_src/body_multiple_params/tutorial004_an_py310.py hl[28] *}
/// info | Информация
@@ -120,9 +120,9 @@ q: str | None = None
///
## Добавление одного body-параметра
## Вложить один body-параметр { #embed-a-single-body-parameter }
Предположим, у вас есть только один body-параметр `item` из Pydantic модели `Item`.
Предположим, у вас есть только один body-параметр `item` из Pydantic-модели `Item`.
По умолчанию, **FastAPI** ожидает получить тело запроса напрямую.
@@ -160,9 +160,9 @@ item: Item = Body(embed=True)
}
```
## Резюме
## Резюме { #recap }
Вы можете добавлять несколько body-параметров вашей *функции операции пути*, несмотря даже на то, что запрос может содержать только одно тело.
Вы можете добавлять несколько body-параметров вашей *функции-обработчика пути*, несмотря даже на то, что запрос может содержать только одно тело.
Но **FastAPI** справится с этим, предоставит правильные данные в вашей функции, а также сделает валидацию и документацию правильной схемы *операции пути*.

View File

@@ -1,41 +1,41 @@
# Body - Вложенные модели
# Body - Вложенные модели { #body-nested-models }
С помощью **FastAPI**, вы можете определять, валидировать, документировать и использовать модели произвольной вложенности (благодаря библиотеке Pydantic).
С помощью **FastAPI** вы можете определять, валидировать, документировать и использовать модели произвольной глубины вложенности (благодаря Pydantic).
## Определение полей содержащих списки
## Поля-списки { #list-fields }
Вы можете определять атрибут как подтип. Например, тип `list` в Python:
Вы можете определить атрибут как подтип. Например, Python-тип `list`:
{* ../../docs_src/body_nested_models/tutorial001_py310.py hl[12] *}
Это приведёт к тому, что обьект `tags` преобразуется в список, несмотря на то что тип его элементов не объявлен.
Это приведёт к тому, что `tags` будет списком, несмотря на то, что тип его элементов не объявлен.
## Определение полей содержащих список с определением типов его элементов
## Поля-списки с параметром типа { #list-fields-with-type-parameter }
Однако в Python есть способ объявления списков с указанием типов для вложенных элементов:
В Python есть специальный способ объявлять списки с внутренними типами, или «параметрами типа»:
### Импортируйте `List` из модуля typing
### Импортируйте `List` из модуля typing { #import-typings-list }
В Python 3.9 и выше вы можете использовать стандартный тип `list` для объявления аннотаций типов, как мы увидим ниже. 💡
Но в версиях Python до 3.9 (начиная с 3.6) сначала вам необходимо импортировать `List` из стандартного модуля `typing` в Python:
Но в версиях Python до 3.9 (начиная с 3.6) сначала вам необходимо импортировать `List` из стандартного модуля `typing`:
{* ../../docs_src/body_nested_models/tutorial002.py hl[1] *}
### Объявление `list` с указанием типов для вложенных элементов
### Объявите `list` с параметром типа { #declare-a-list-with-a-type-parameter }
Объявление типов для элементов (внутренних типов) вложенных в такие типы как `list`, `dict`, `tuple`:
Для объявления типов, у которых есть параметры типа (внутренние типы), таких как `list`, `dict`, `tuple`:
* Если у вас Python версии ниже чем 3.9, импортируйте их аналог из модуля `typing`
* Передайте внутренний(ие) тип(ы) как "параметры типа", используя квадратные скобки: `[` и `]`
* Если у вас Python версии ниже 3.9, импортируйте их аналоги из модуля `typing`
* Передайте внутренний(ие) тип(ы) как «параметры типа», используя квадратные скобки: `[` и `]`
В Python версии 3.9 это будет выглядеть так:
В Python 3.9 это будет:
```Python
my_list: list[str]
```
В версиях Python до 3.9 это будет выглядеть так:
В версиях Python до 3.9 это будет:
```Python
from typing import List
@@ -47,17 +47,17 @@ my_list: List[str]
Используйте этот же стандартный синтаксис для атрибутов модели с внутренними типами.
Таким образом, в нашем примере мы можем явно указать тип данных для поля `tags` как "список строк":
Таким образом, в нашем примере мы можем явно указать тип данных для поля `tags` как «список строк»:
{* ../../docs_src/body_nested_models/tutorial002_py310.py hl[12] *}
## Типы множеств
## Типы множеств { #set-types }
Но затем мы подумали и поняли, что теги не должны повторяться и, вероятно, они должны быть уникальными строками.
Но затем мы подумали и поняли, что теги не должны повторяться, вероятно, это должны быть уникальные строки.
И в Python есть специальный тип данных для множеств уникальных элементов - `set`.
И в Python есть специальный тип данных для множеств уникальных элементов `set`.
Тогда мы можем обьявить поле `tags` как множество строк:
Тогда мы можем объявить поле `tags` как множество строк:
{* ../../docs_src/body_nested_models/tutorial003_py310.py hl[12] *}
@@ -67,23 +67,23 @@ my_list: List[str]
И они также будут соответствующим образом аннотированы / задокументированы.
## Вложенные Модели
## Вложенные модели { #nested-models }
У каждого атрибута Pydantic-модели есть тип.
Но этот тип может сам быть другой моделью Pydantic.
Но этот тип сам может быть другой моделью Pydantic.
Таким образом вы можете объявлять глубоко вложенные JSON "объекты" с определёнными именами атрибутов, типами и валидацией.
Таким образом, вы можете объявлять глубоко вложенные JSON «объекты» с определёнными именами атрибутов, типами и валидацией.
Всё это может быть произвольно вложенным.
### Определение подмодели
### Определение подмодели { #define-a-submodel }
Например, мы можем определить модель `Image`:
{* ../../docs_src/body_nested_models/tutorial004_py310.py hl[7:9] *}
### Использование вложенной модели в качестве типа
### Использование подмодели как типа { #use-the-submodel-as-a-type }
Также мы можем использовать эту модель как тип атрибута:
@@ -107,30 +107,30 @@ my_list: List[str]
Ещё раз: сделав такое объявление, с помощью **FastAPI** вы получите:
* Поддержку редакторов IDE (автодополнение и т.д), даже для вложенных моделей
* Поддержку редактора кода (автозавершение и т. д.), даже для вложенных моделей
* Преобразование данных
* Валидацию данных
* Автоматическую документацию
## Особые типы и валидация
## Особые типы и валидация { #special-types-and-validation }
Помимо обычных простых типов, таких как `str`, `int`, `float`, и т.д. Вы можете использовать более сложные базовые типы, которые наследуются от типа `str`.
Помимо обычных простых типов, таких как `str`, `int`, `float` и т.д., вы можете использовать более сложные простые типы, которые наследуются от `str`.
Чтобы увидеть все варианты, которые у вас есть, ознакомьтесь с документацией <a href="https://docs.pydantic.dev/latest/concepts/types/" class="external-link" target="_blank">по необычным типам Pydantic</a>. Вы увидите некоторые примеры в следующей главе.
Чтобы увидеть все варианты, которые у вас есть, ознакомьтесь с <a href="https://docs.pydantic.dev/latest/concepts/types/" class="external-link" target="_blank">обзором типов Pydantic</a>. Вы увидите некоторые примеры в следующей главе.
Например, так как в модели `Image` у нас есть поле `url`, то мы можем объявить его как тип `HttpUrl` из модуля Pydantic вместо типа `str`:
Например, так как в модели `Image` у нас есть поле `url`, то мы можем объявить его как тип `HttpUrl` из Pydantic вместо типа `str`:
{* ../../docs_src/body_nested_models/tutorial005_py310.py hl[2,8] *}
Строка будет проверена на соответствие допустимому URL-адресу и задокументирована в JSON схему / OpenAPI.
Строка будет проверена на соответствие допустимому URL-адресу и задокументирована в JSON Schema / OpenAPI как таковая.
## Атрибуты, содержащие списки подмоделей
## Атрибуты, содержащие списки подмоделей { #attributes-with-lists-of-submodels }
Вы также можете использовать модели Pydantic в качестве типов вложенных в `list`, `set` и т.д:
Вы также можете использовать модели Pydantic в качестве подтипов для `list`, `set` и т.д.:
{* ../../docs_src/body_nested_models/tutorial006_py310.py hl[18] *}
Такая реализация будет ожидать (конвертировать, валидировать, документировать и т.д) JSON-содержимое в следующем формате:
Такая реализация будет ожидать (конвертировать, валидировать, документировать и т.д.) JSON-содержимое в следующем формате:
```JSON hl_lines="11"
{
@@ -162,7 +162,7 @@ my_list: List[str]
///
## Глубоко вложенные модели
## Глубоко вложенные модели { #deeply-nested-models }
Вы можете определять модели с произвольным уровнем вложенности:
@@ -174,15 +174,15 @@ my_list: List[str]
///
## Тела с чистыми списками элементов
## Тела с чистыми списками элементов { #bodies-of-pure-lists }
Если верхний уровень значения тела JSON-объекта представляет собой JSON `array` (в Python - `list`), вы можете объявить тип в параметре функции, так же, как в моделях Pydantic:
Если верхний уровень значения тела JSON-объекта представляет собой JSON `array` (в Python `list`), вы можете объявить тип в параметре функции, так же как в моделях Pydantic:
```Python
images: List[Image]
```
в Python 3.9 и выше:
или в Python 3.9 и выше:
```Python
images: list[Image]
@@ -192,33 +192,33 @@ images: list[Image]
{* ../../docs_src/body_nested_models/tutorial008_py39.py hl[13] *}
## Универсальная поддержка редактора
## Поддержка редактора кода везде { #editor-support-everywhere }
И вы получаете поддержку редактора везде.
И вы получаете поддержку редактора кода везде.
Даже для элементов внутри списков:
<img src="/img/tutorial/body-nested-models/image01.png">
Вы не могли бы получить такую поддержку редактора, если бы работали напрямую с `dict`, а не с моделями Pydantic.
Вы не могли бы получить такую поддержку редактора кода, если бы работали напрямую с `dict`, а не с моделями Pydantic.
Но вы также не должны беспокоиться об этом, входящие словари автоматически конвертируются, а ваш вывод также автоматически преобразуется в формат JSON.
## Тела запросов с произвольными словарями (`dict` )
## Тела запросов с произвольными словарями (`dict`) { #bodies-of-arbitrary-dicts }
Вы также можете объявить тело запроса как `dict` с ключами определенного типа и значениями другого типа данных.
Вы также можете объявить тело запроса как `dict` с ключами определённого типа и значениями другого типа.
Без необходимости знать заранее, какие значения являются допустимыми для имён полей/атрибутов (как это было бы в случае с моделями Pydantic).
Это было бы полезно, если вы хотите получить ключи, которые вы еще не знаете.
Это было бы полезно, если вы хотите получить ключи, которые вы ещё не знаете.
---
Другой полезный случай - когда вы хотите чтобы ключи были другого типа данных, например, `int`.
Другой полезный случай когда вы хотите, чтобы ключи были другого типа данных, например, `int`.
Именно это мы сейчас и увидим здесь.
В этом случае вы принимаете `dict`, пока у него есть ключи типа `int` со значениями типа `float`:
В этом случае вы принимаете любой `dict`, пока у него есть ключи типа `int` со значениями типа `float`:
{* ../../docs_src/body_nested_models/tutorial009_py39.py hl[7] *}
@@ -228,19 +228,19 @@ images: list[Image]
Но Pydantic обеспечивает автоматическое преобразование данных.
Это значит, что даже если пользователи вашего API могут отправлять только строки в качестве ключей, при условии, что эти строки содержат целые числа, Pydantic автоматический преобразует и валидирует эти данные.
Это значит, что даже если клиенты вашего API могут отправлять только строки в качестве ключей, при условии, что эти строки содержат целые числа, Pydantic автоматически преобразует и валидирует эти данные.
А `dict`, с именем `weights`, который вы получите в качестве ответа Pydantic, действительно будет иметь ключи типа `int` и значения типа `float`.
А `dict`, который вы получите как `weights`, действительно будет иметь ключи типа `int` и значения типа `float`.
///
## Резюме
## Резюме { #recap }
С помощью **FastAPI** вы получаете максимальную гибкость, предоставляемую моделями Pydantic, сохраняя при этом простоту, краткость и элегантность вашего кода.
И дополнительно вы получаете:
* Поддержку редактора (автодополнение доступно везде!)
* Поддержку редактора кода (автозавершение доступно везде!)
* Преобразование данных (также известно как парсинг / сериализация)
* Валидацию данных
* Документацию схемы данных

View File

@@ -1,16 +1,16 @@
# Body - Обновления
# Body - Обновления { #body-updates }
## Полное обновление с помощью `PUT`
## Обновление с заменой при помощи `PUT` { #update-replacing-with-put }
Для полного обновления элемента можно воспользоваться операцией <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT" class="external-link" target="_blank">HTTP `PUT`</a>.
Вы можете использовать функцию `jsonable_encoder` для преобразования входных данных в JSON, так как нередки случаи, когда работать можно только с простыми типами данных (например, для хранения в NoSQL-базе данных).
Вы можете использовать `jsonable_encoder`, чтобы преобразовать входные данные в данные, которые можно сохранить как JSON (например, в NoSQL-базе данных). Например, преобразование `datetime` в `str`.
{* ../../docs_src/body_updates/tutorial001_py310.py hl[28:33] *}
`PUT` используется для получения данных, которые должны полностью заменить существующие данные.
### Предупреждение о замене
### Предупреждение о замене { #warning-about-replacing }
Это означает, что если вы хотите обновить элемент `bar`, используя `PUT` с телом, содержащим:
@@ -26,7 +26,7 @@
И данные будут сохранены с этим "новым" `tax`, равным `10,5`.
## Частичное обновление с помощью `PATCH`
## Частичное обновление с помощью `PATCH` { #partial-updates-with-patch }
Также можно использовать <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH" class="external-link" target="_blank">HTTP `PATCH`</a> операцию для *частичного* обновления данных.
@@ -44,25 +44,41 @@
///
### Использование параметра `exclude_unset` в Pydantic
### Использование параметра `exclude_unset` в Pydantic { #using-pydantics-exclude-unset-parameter }
Если необходимо выполнить частичное обновление, то очень полезно использовать параметр `exclude_unset` в методе `.dict()` модели Pydantic.
Если необходимо выполнить частичное обновление, то очень полезно использовать параметр `exclude_unset` в методе `.model_dump()` модели Pydantic.
Например, `item.dict(exclude_unset=True)`.
Например, `item.model_dump(exclude_unset=True)`.
/// info | Информация
В Pydantic v1 метод назывался `.dict()`, в Pydantic v2 он помечен как устаревший (но все еще поддерживается) и переименован в `.model_dump()`.
Примеры здесь используют `.dict()` для совместимости с Pydantic v1, но если вы можете использовать Pydantic v2, лучше используйте `.model_dump()`.
///
В результате будет сгенерирован словарь, содержащий только те данные, которые были заданы при создании модели `item`, без учета значений по умолчанию. Затем вы можете использовать это для создания словаря только с теми данными, которые были установлены (отправлены в запросе), опуская значения по умолчанию:
{* ../../docs_src/body_updates/tutorial002_py310.py hl[32] *}
### Использование параметра `update` в Pydantic
### Использование параметра `update` в Pydantic { #using-pydantics-update-parameter }
Теперь можно создать копию существующей модели, используя `.copy()`, и передать параметр `update` с `dict`, содержащим данные для обновления.
Теперь можно создать копию существующей модели, используя `.model_copy()`, и передать параметр `update` с `dict`, содержащим данные для обновления.
Например, `stored_item_model.copy(update=update_data)`:
/// info | Информация
В Pydantic v1 метод назывался `.copy()`, в Pydantic v2 он помечен как устаревший (но все еще поддерживается) и переименован в `.model_copy()`.
Примеры здесь используют `.copy()` для совместимости с Pydantic v1, но если вы можете использовать Pydantic v2, лучше используйте `.model_copy()`.
///
Например, `stored_item_model.model_copy(update=update_data)`:
{* ../../docs_src/body_updates/tutorial002_py310.py hl[33] *}
### Кратко о частичном обновлении
### Кратко о частичном обновлении { #partial-updates-recap }
В целом, для применения частичных обновлений необходимо:
@@ -73,7 +89,7 @@
* Таким образом, можно обновлять только те значения, которые действительно установлены пользователем, вместо того чтобы переопределять значения, уже сохраненные в модели по умолчанию.
* Создать копию хранимой модели, обновив ее атрибуты полученными частичными обновлениями (с помощью параметра `update`).
* Преобразовать скопированную модель в то, что может быть сохранено в вашей БД (например, с помощью `jsonable_encoder`).
* Это сравнимо с повторным использованием метода модели `.dict()`, но при этом происходит проверка (и преобразование) значений в типы данных, которые могут быть преобразованы в JSON, например, `datetime` в `str`.
* Это сравнимо с повторным использованием метода модели `.model_dump()`, но при этом происходит проверка (и преобразование) значений в типы данных, которые могут быть преобразованы в JSON, например, `datetime` в `str`.
* Сохранить данные в своей БД.
* Вернуть обновленную модель.

View File

@@ -1,40 +1,40 @@
# Тело запроса
# Тело запроса { #request-body }
Когда вам необходимо отправить данные из клиента (допустим, браузера) в ваш API, вы отправляете их как **тело запроса**.
Когда вам необходимо отправить данные из клиента (например, браузера) в ваш API, вы отправляете их как **тело запроса**.
Тело **запроса** --- это данные, отправляемые клиентом в ваш API. Тело **ответа** --- это данные, которые ваш API отправляет клиенту.
Тело **запроса** это данные, отправляемые клиентом в ваш API. Тело **ответа** это данные, которые ваш API отправляет клиенту.
Ваш API почти всегда отправляет тело **ответа**. Но клиентам не обязательно всегда отправлять тело **запроса**.
Ваш API почти всегда должен отправлять тело **ответа**. Но клиентам не обязательно всегда отправлять **тело запроса**: иногда они запрашивают только путь, возможно с некоторыми параметрами запроса, но без тела.
Чтобы объявить тело **запроса**, необходимо использовать модели <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a>, со всей их мощью и преимуществами.
Чтобы объявить тело **запроса**, используйте модели <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a>, со всей их мощью и преимуществами.
/// info | Информация
Чтобы отправить данные, необходимо использовать один из методов: `POST` (обычно), `PUT`, `DELETE` или `PATCH`.
Чтобы отправить данные, используйте один из методов: `POST` (чаще всего), `PUT`, `DELETE` или `PATCH`.
Отправка тела с запросом `GET` имеет неопределенное поведение в спецификациях, тем не менее, оно поддерживается FastAPI только для очень сложных/экстремальных случаев использования.
Отправка тела с запросом `GET` имеет неопределённое поведение в спецификациях, тем не менее это поддерживается FastAPI, но только для очень сложных/крайних случаев использования.
Поскольку это не рекомендуется, интерактивная документация со Swagger UI не будет отображать информацию для тела при использовании метода GET, а промежуточные прокси-серверы могут не поддерживать такой вариант запроса.
Поскольку это не рекомендуется, интерактивная документация со Swagger UI не будет отображать информацию для тела при использовании `GET`, а промежуточные прокси-серверы могут не поддерживать такой вариант запроса.
///
## Импортирование `BaseModel` из Pydantic
## Импортируйте `BaseModel` из Pydantic { #import-pydantics-basemodel }
Первое, что вам необходимо сделать, это импортировать `BaseModel` из пакета `pydantic`:
Первое, что нужно сделать, импортировать `BaseModel` из пакета `pydantic`:
{* ../../docs_src/body/tutorial001.py hl[4] *}
{* ../../docs_src/body/tutorial001_py310.py hl[2] *}
## Создание вашей собственной модели
## Создайте модель данных { #create-your-data-model }
После этого вы описываете вашу модель данных как класс, наследующий от `BaseModel`.
Затем опишите свою модель данных как класс, наследующийся от `BaseModel`.
Используйте аннотации типов Python для всех атрибутов:
Используйте стандартные типы Python для всех атрибутов:
{* ../../docs_src/body/tutorial001.py hl[7:11] *}
{* ../../docs_src/body/tutorial001_py310.py hl[5:9] *}
Также как и при описании параметров запроса, когда атрибут модели имеет значение по умолчанию, он является необязательным. Иначе он обязателен. Используйте `None`, чтобы сделать его необязательным без использования конкретных значений по умолчанию.
Так же, как при объявлении параметров запроса: когда атрибут модели имеет значение по умолчанию, он не обязателен. Иначе он обязателен. Используйте `None`, чтобы сделать его просто необязательным.
Например, модель выше описывает вот такой JSON "объект" (или словарь Python):
Например, модель выше описывает такой JSON "объект" (или Python `dict`):
```JSON
{
@@ -45,7 +45,7 @@
}
```
...поскольку `description` и `tax` являются необязательными (с `None` в качестве значения по умолчанию), вот такой JSON "объект" также подходит:
...так как `description` и `tax` являются необязательными (со значением по умолчанию `None`), такой JSON "объект" тоже будет корректным:
```JSON
{
@@ -54,109 +54,119 @@
}
```
## Объявление как параметра функции
## Объявите её как параметр { #declare-it-as-a-parameter }
Чтобы добавить параметр к вашему *обработчику*, объявите его также, как вы объявляли параметры пути или параметры запроса:
Чтобы добавить её в вашу *операцию пути*, объявите её так же, как вы объявляли параметры пути и параметры запроса:
{* ../../docs_src/body/tutorial001.py hl[18] *}
{* ../../docs_src/body/tutorial001_py310.py hl[16] *}
...и укажите созданную модель в качестве типа параметра, `Item`.
...и укажите тип параметра как созданную вами модель, `Item`.
## Результаты
## Результаты { #results }
Всего лишь с помощью аннотации типов Python, **FastAPI**:
Всего лишь с этой аннотацией типов Python **FastAPI**:
* Читает тело запроса как JSON.
* Приводит к соответствующим типам (если есть необходимость).
* Проверяет корректность данных.
* Если данные некорректны, будет возращена читаемая и понятная ошибка, показывающая что именно и в каком месте некорректно в данных.
* Складывает полученные данные в параметр `item`.
* Поскольку внутри функции вы объявили его с типом `Item`, то теперь у вас есть поддержка со стороны редактора (автодополнение и т.п.) для всех атрибутов и их типов.
* Генерирует декларативное описание модели в виде <a href="https://json-schema.org" class="external-link" target="_blank">JSON Schema</a>, так что вы можете его использовать где угодно, если это имеет значение для вашего проекта.
* Эти схемы являются частью сгенерированной схемы OpenAPI и используются для автоматического документирования <abbr title="Пользовательских интерфейсов (User Interfaces)">UI</abbr>.
* Считает тело запроса как JSON.
* Приведёт данные к соответствующим типам (если потребуется).
* Проведёт валидацию данных.
* Если данные некорректны, вернёт понятную и наглядную ошибку, указывающую, где именно и что было некорректно.
* Передаст полученные данные в параметр `item`.
* Поскольку внутри функции вы объявили его с типом `Item`, у вас будет поддержка со стороны редактора кода (автозавершение и т. п.) для всех атрибутов и их типов.
* Сгенерирует определения <a href="https://json-schema.org" class="external-link" target="_blank">JSON Schema</a> для вашей модели; вы можете использовать их и в других местах, если это имеет смысл для вашего проекта.
* Эти схемы будут частью сгенерированной схемы OpenAPI и будут использоваться автоматической документацией <abbr title="User Interfaces Пользовательские интерфейсы">UIs</abbr>.
## Автоматическое документирование
## Автоматическая документация { #automatic-docs }
Схема JSON ваших моделей будет частью сгенерированной схемы OpenAPI и будет отображена в интерактивной документации API:
JSON Schema ваших моделей будет частью сгенерированной схемы OpenAPI и будет отображаться в интерактивной документации API:
<img src="/img/tutorial/body/image01.png">
Также она будет указана в документации по API внутри каждой *операции пути*, в которой используются:
А также они будут использоваться в документации API внутри каждой *операции пути*, где это требуется:
<img src="/img/tutorial/body/image02.png">
## Поддержка редактора
## Поддержка редактора кода { #editor-support }
В вашем редакторе внутри вашей функции у вас будут подсказки по типам и автодополнение (это не будет работать, если вы получаете словарь вместо модели Pydantic):
В вашем редакторе кода внутри функции вы получите подсказки по типам и автозавершение повсюду (этого бы не было, если бы вы получали `dict` вместо модели Pydantic):
<img src="/img/tutorial/body/image03.png">
Также вы будете получать ошибки в случае несоответствия типов:
Также вы получите проверку ошибок при некорректных операциях с типами:
<img src="/img/tutorial/body/image04.png">
Это не случайно, весь фреймворк построен вокруг такого дизайна.
Это не случайность — весь фреймворк построен вокруг такого дизайна.
И это все тщательно протестировано еще на этапе разработки дизайна, до реализации, чтобы это работало со всеми редакторами.
И это было тщательно протестировано ещё на этапе проектирования, до реализации, чтобы убедиться, что всё будет работать со всеми редакторами.
Для поддержки этого даже были внесены некоторые изменения в сам Pydantic.
В сам Pydantic даже были внесены некоторые изменения для поддержки этого.
На всех предыдущих скриншотах используется <a href="https://code.visualstudio.com" class="external-link" target="_blank">Visual Studio Code</a>.
Предыдущие скриншоты сделаны в <a href="https://code.visualstudio.com" class="external-link" target="_blank">Visual Studio Code</a>.
Но у вас будет такая же поддержка и с <a href="https://www.jetbrains.com/pycharm/" class="external-link" target="_blank">PyCharm</a>, и вообще с любым редактором Python:
Но вы получите такую же поддержку редактора кода в <a href="https://www.jetbrains.com/pycharm/" class="external-link" target="_blank">PyCharm</a> и большинстве других редакторов Python:
<img src="/img/tutorial/body/image05.png">
/// tip | Подсказка
/// tip | Совет
Если вы используете <a href="https://www.jetbrains.com/pycharm/" class="external-link" target="_blank">PyCharm</a> в качестве редактора, то вам стоит попробовать плагин <a href="https://github.com/koxudaxi/pydantic-pycharm-plugin/" class="external-link" target="_blank">Pydantic PyCharm Plugin</a>.
Если вы используете <a href="https://www.jetbrains.com/pycharm/" class="external-link" target="_blank">PyCharm</a> в качестве редактора кода, вы можете использовать плагин <a href="https://github.com/koxudaxi/pydantic-pycharm-plugin/" class="external-link" target="_blank">Pydantic PyCharm Plugin</a>.
Он улучшает поддержку редактором моделей Pydantic в части:
Он улучшает поддержку моделей Pydantic в редакторе кода, включая:
* автодополнения,
* проверки типов,
* рефакторинга,
* поиска,
* инспектирования.
* автозавершение
* проверки типов
* рефакторинг
* поиск
* инспекции
///
## Использование модели
## Использование модели { #use-the-model }
Внутри функции вам доступны все атрибуты объекта модели напрямую:
{* ../../docs_src/body/tutorial002.py hl[21] *}
{* ../../docs_src/body/tutorial002_py310.py *}
## Тело запроса + параметры пути
/// info | Информация
Вы можете одновременно объявлять параметры пути и тело запроса.
В Pydantic v1 метод назывался `.dict()`, в Pydantic v2 он был помечен как устаревший (но всё ещё поддерживается) и переименован в `.model_dump()`.
**FastAPI** распознает, какие параметры функции соответствуют параметрам пути и должны быть **получены из пути**, а какие параметры функции, объявленные как модели Pydantic, должны быть **получены из тела запроса**.
{* ../../docs_src/body/tutorial003.py hl[17:18] *}
## Тело запроса + параметры пути + параметры запроса
Вы также можете одновременно объявить параметры для **пути**, **запроса** и **тела запроса**.
**FastAPI** распознает каждый из них и возьмет данные из правильного источника.
{* ../../docs_src/body/tutorial004.py hl[18] *}
Параметры функции распознаются следующим образом:
* Если параметр также указан в **пути**, то он будет использоваться как параметр пути.
* Если аннотация типа параметра содержит **примитивный тип** (`int`, `float`, `str`, `bool` и т.п.), он будет интерпретирован как параметр **запроса**.
* Если аннотация типа параметра представляет собой **модель Pydantic**, он будет интерпретирован как параметр **тела запроса**.
/// note | Заметка
FastAPI понимает, что значение параметра `q` не является обязательным, потому что имеет значение по умолчанию `= None`.
Аннотация `Optional` в `Optional[str]` не используется FastAPI, но помогает вашему редактору лучше понимать ваш код и обнаруживать ошибки.
Примеры здесь используют `.dict()` для совместимости с Pydantic v1, но если вы можете использовать Pydantic v2, используйте `.model_dump()`.
///
## Без Pydantic
## Тело запроса + параметры пути { #request-body-path-parameters }
Если вы не хотите использовать модели Pydantic, вы все еще можете использовать параметры **тела запроса**. Читайте в документации раздел [Тело - Несколько параметров: Единичные значения в теле](body-multiple-params.md#_2){.internal-link target=_blank}.
Вы можете одновременно объявить параметры пути и тело запроса.
**FastAPI** распознает, что параметры функции, соответствующие параметрам пути, должны быть **получены из пути**, а параметры функции, объявленные как модели Pydantic, должны быть **получены из тела запроса**.
{* ../../docs_src/body/tutorial003_py310.py hl[15:16] *}
## Тело запроса + параметры пути + параметры запроса { #request-body-path-query-parameters }
Вы также можете одновременно объявить параметры **тела**, **пути** и **запроса**.
**FastAPI** распознает каждый из них и возьмёт данные из правильного источника.
{* ../../docs_src/body/tutorial004_py310.py hl[16] *}
Параметры функции будут распознаны следующим образом:
* Если параметр также объявлен в **пути**, он будет использоваться как параметр пути.
* Если параметр имеет **скалярный тип** (например, `int`, `float`, `str`, `bool` и т. п.), он будет интерпретирован как параметр **запроса**.
* Если параметр объявлен как тип **модели Pydantic**, он будет интерпретирован как **тело** запроса.
/// note | Заметка
FastAPI понимает, что значение `q` не является обязательным из-за значения по умолчанию `= None`.
Аннотации типов `str | None` (Python 3.10+) или `Union[str, None]` (Python 3.8+) не используются FastAPI для определения обязательности; он узнает, что параметр не обязателен, потому что у него есть значение по умолчанию `= None`.
Но добавление аннотаций типов позволит вашему редактору кода лучше вас поддерживать и обнаруживать ошибки.
///
## Без Pydantic { #without-pydantic }
Если вы не хотите использовать модели Pydantic, вы также можете использовать параметры **Body**. См. раздел документации [Тело — Несколько параметров: Единичные значения в теле](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}.

View File

@@ -1,4 +1,4 @@
# Модели параметров cookie
# Модели параметров cookie { #cookie-parameter-models }
Если у вас есть группа **cookies**, которые связаны между собой, вы можете создать **Pydantic-модель** для их объявления. 🍪
@@ -16,7 +16,7 @@
///
## Pydantic-модель для cookies
## Pydantic-модель для cookies { #cookies-with-a-pydantic-model }
Объявите параметры **cookie**, которые вам нужны, в **Pydantic-модели**, а затем объявите параметр как `Cookie`:
@@ -24,7 +24,7 @@
**FastAPI** **извлечёт** данные для **каждого поля** из **cookies**, полученных в запросе, и выдаст вам объявленную Pydantic-модель.
## Проверка сгенерированной документации
## Проверка сгенерированной документации { #check-the-docs }
Вы можете посмотреть объявленные cookies в графическом интерфейсе Документации по пути `/docs`:
@@ -42,7 +42,7 @@
///
## Запрет дополнительных cookies
## Запрет дополнительных cookies { #forbid-extra-cookies }
В некоторых случаях (не особо часто встречающихся) вам может понадобиться **ограничить** cookies, которые вы хотите получать.
@@ -65,12 +65,12 @@
"type": "extra_forbidden",
"loc": ["cookie", "santa_tracker"],
"msg": "Extra inputs are not permitted",
"input": "good-list-please"
"input": "good-list-please",
}
]
}
```
## Заключение
## Заключение { #summary }
Вы можете использовать **Pydantic-модели** для объявления <abbr title="Съешьте последнюю печеньку, прежде чем уйти. 🍪">**cookies**</abbr> в **FastAPI**. 😎

View File

@@ -1,20 +1,20 @@
# Параметры Cookie
# Параметры Cookie { #cookie-parameters }
Вы можете задать параметры Cookie таким же способом, как `Query` и `Path` параметры.
## Импорт `Cookie`
## Импорт `Cookie` { #import-cookie }
Сначала импортируйте `Cookie`:
{* ../../docs_src/cookie_params/tutorial001_py310.py hl[1] *}
{* ../../docs_src/cookie_params/tutorial001_an_py310.py hl[3] *}
## Объявление параметров `Cookie`
## Объявление параметров `Cookie` { #declare-cookie-parameters }
Затем объявляйте параметры cookie, используя ту же структуру, что и с `Path` и `Query`.
Первое значение - это значение по умолчанию, вы можете передать все дополнительные параметры проверки или аннотации:
Вы можете задать значение по умолчанию, а также все дополнительные параметры валидации или аннотации:
{* ../../docs_src/cookie_params/tutorial001_py310.py hl[7] *}
{* ../../docs_src/cookie_params/tutorial001_an_py310.py hl[9] *}
/// note | Технические детали
@@ -30,6 +30,16 @@
///
## Резюме
/// info | Дополнительная информация
Имейте в виду, что, поскольку браузеры обрабатывают cookies особым образом и «за кулисами», они не позволяют JavaScript просто так получать к ним доступ.
Если вы откроете интерфейс документации API на `/docs`, вы сможете увидеть документацию по cookies для ваших операций пути.
Но даже если вы заполните данные и нажмёте «Execute», поскольку UI документации работает с JavaScript, cookies отправлены не будут, и вы увидите сообщение об ошибке, как будто вы не указали никаких значений.
///
## Резюме { #recap }
Объявляйте cookies с помощью `Cookie`, используя тот же общий шаблон, что и `Query`, и `Path`.

View File

@@ -1,10 +1,10 @@
# CORS (Cross-Origin Resource Sharing)
# CORS (Cross-Origin Resource Sharing) { #cors-cross-origin-resource-sharing }
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" class="external-link" target="_blank">Понятие CORS или "Cross-Origin Resource Sharing"</a> относится к ситуациям, при которых запущенный в браузере фронтенд содержит JavaScript-код, который взаимодействует с бэкендом, находящимся на другом "источнике" ("origin").
## Источник
## Источник { #origin }
Источник - это совокупность протокола (`http`, `https`), домена (`myapp.com`, `localhost`, `localhost.tiangolo.com`) и порта (`80`, `443`, `8080`).
Источник это совокупность протокола (`http`, `https`), домена (`myapp.com`, `localhost`, `localhost.tiangolo.com`) и порта (`80`, `443`, `8080`).
Поэтому это три разных источника:
@@ -12,69 +12,72 @@
* `https://localhost`
* `http://localhost:8080`
Даже если они все расположены в `localhost`, они используют разные протоколы и порты, а значит, являются разными источниками.
Даже если они все расположены в `localhost`, они используют разные протоколы или порты, а значит, являются разными источниками.
## Шаги
## Шаги { #steps }
Допустим, у вас есть фронтенд, запущенный в браузере по адресу `http://localhost:8080`, и его JavaScript-код пытается взаимодействовать с бэкендом, запущенным по адресу `http://localhost` (поскольку мы не указали порт, браузер по умолчанию будет использовать порт `80`).
Допустим, у вас есть фронтенд, запущенный в браузере по адресу `http://localhost:8080`, и его JavaScript-код пытается взаимодействовать с бэкендом, запущенным по адресу `http://localhost` (поскольку мы не указали порт, браузер по умолчанию будет использовать порт `80`).
Затем браузер отправит бэкенду HTTP-запрос `OPTIONS`, и если бэкенд вернёт соответствующие заголовки для авторизации взаимодействия с другим источником (`http://localhost:8080`), то браузер разрешит JavaScript-коду на фронтенде отправить запрос на этот бэкенд.
Затем браузер отправит на бэкенд на `:80` HTTP-запрос `OPTIONS`, и если бэкенд вернёт соответствующие HTTP-заголовки, авторизующие взаимодействие с другим источником (`http://localhost:8080`), то браузер на `:8080` разрешит JavaScript на фронтенде отправить свой запрос на бэкенд на `:80`.
Чтобы это работало, у бэкенда должен быть список "разрешённых источников" ("allowed origins").
Чтобы это работало, у бэкенда на `:80` должен быть список "разрешённых источников" ("allowed origins").
В таком случае этот список должен содержать `http://localhost:8080`, чтобы фронтенд работал корректно.
В таком случае этот список должен содержать `http://localhost:8080`, чтобы фронтенд на `:8080` работал корректно.
## Подстановочный символ `"*"`
## Подстановочный символ "*" { #wildcards }
В качестве списка источников можно указать подстановочный символ `"*"` ("wildcard"), чтобы разрешить любые источники.
Но тогда не будут разрешены некоторые виды взаимодействия, включая всё связанное с учётными данными: куки, заголовки Authorization с Bearer-токенами наподобие тех, которые мы использовали ранее и т.п.
Но тогда будут разрешены только некоторые виды взаимодействия, и всё, что связано с учётными данными, будет исключено: куки, HTTP-заголовки Authorization, как при использовании Bearer-токенов, и т.п.
Поэтому, чтобы всё работало корректно, лучше явно указывать список разрешённых источников.
## Использование `CORSMiddleware`
## Использование `CORSMiddleware` { #use-corsmiddleware }
Вы можете настроить этот механизм в вашем **FastAPI** приложении, используя `CORSMiddleware`.
Вы можете настроить это в вашем **FastAPI**-приложении, используя `CORSMiddleware`.
* Импортируйте `CORSMiddleware`.
* Создайте список разрешённых источников (в виде строк).
* Добавьте его как "middleware" к вашему **FastAPI** приложению.
* Добавьте его как "middleware" (промежуточный слой) к вашему **FastAPI**-приложению.
Вы также можете указать, разрешает ли ваш бэкенд использование:
* Учётных данных (включая заголовки Authorization, куки и т.п.).
* Учётных данных (HTTP-заголовки Authorization, куки и т.п.).
* Отдельных HTTP-методов (`POST`, `PUT`) или всех вместе, используя `"*"`.
* Отдельных HTTP-заголовков или всех вместе, используя `"*"`.
{* ../../docs_src/cors/tutorial001.py hl[2,6:11,13:19] *}
`CORSMiddleware` использует для параметров "запрещающие" значения по умолчанию, поэтому вам нужно явным образом разрешить использование отдельных источников, методов или заголовков, чтобы браузеры могли использовать их в кросс-доменном контексте.
`CORSMiddleware` использует "запрещающие" значения по умолчанию, поэтому вам нужно явным образом разрешить использование отдельных источников, методов или заголовков, чтобы браузеры могли использовать их в кросс-доменном контексте.
Поддерживаются следующие аргументы:
* `allow_origins` - Список источников, на которые разрешено выполнять кросс-доменные запросы. Например, `['https://example.org', 'https://www.example.org']`. Можно использовать `['*']`, чтобы разрешить любые источники.
* `allow_origin_regex` - Регулярное выражение для определения источников, на которые разрешено выполнять кросс-доменные запросы. Например, `'https://.*\.example\.org'`.
* `allow_methods` - Список HTTP-методов, которые разрешены для кросс-доменных запросов. По умолчанию равно `['GET']`. Можно использовать `['*']`, чтобы разрешить все стандартные методы.
* `allow_headers` - Список HTTP-заголовков, которые должны поддерживаться при кросс-доменных запросах. По умолчанию равно `[]`. Можно использовать `['*']`, чтобы разрешить все заголовки. Заголовки `Accept`, `Accept-Language`, `Content-Language` и `Content-Type` всегда разрешены для <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests" class="external-link" rel="noopener" target="_blank">простых CORS-запросов</a>.
* `allow_credentials` - указывает, что куки разрешены в кросс-доменных запросах. По умолчанию равно `False`. Также, `allow_origins` нельзя присвоить `['*']`, если разрешено использование учётных данных. В таком случае должен быть указан список источников.
* `expose_headers` - Указывает любые заголовки ответа, которые должны быть доступны браузеру. По умолчанию равно `[]`.
* `max_age` - Устанавливает максимальное время в секундах, в течение которого браузер кэширует CORS-ответы. По умолчанию равно `600`.
* `allow_origin_regex` - Регулярное выражение для определения источников, на которые разрешено выполнять кросс-доменные запросы. Например, `'https://.*\.example\.org'`.
* `allow_methods` - Список HTTP-методов, которые разрешены для кросс-доменных запросов. По умолчанию `['GET']`. Можно использовать `['*']`, чтобы разрешить все стандартные методы.
* `allow_headers` - Список HTTP-заголовков запроса, которые должны поддерживаться при кросс-доменных запросах. По умолчанию `[]`. Можно использовать `['*']`, чтобы разрешить все заголовки. Заголовки `Accept`, `Accept-Language`, `Content-Language` и `Content-Type` всегда разрешены для <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests" class="external-link" rel="noopener" target="_blank">простых CORS-запросов</a>.
* `allow_credentials` - Указывает, что куки разрешены в кросс-доменных запросах. По умолчанию `False`.
Ни один из параметров `allow_origins`, `allow_methods` и `allow_headers` не может быть установлен в `['*']`, если `allow_credentials` имеет значение `True`. Все они должны быть <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#credentialed_requests_and_wildcards" class="external-link" rel="noopener" target="_blank">указаны явно</a>.
* `expose_headers` - Указывает любые заголовки ответа, которые должны быть доступны браузеру. По умолчанию `[]`.
* `max_age` - Устанавливает максимальное время в секундах, в течение которого браузер кэширует CORS-ответы. По умолчанию `600`.
`CORSMiddleware` отвечает на два типа HTTP-запросов...
### CORS-запросы с предварительной проверкой
### CORS-запросы с предварительной проверкой { #cors-preflight-requests }
Это любые `OPTIONS` запросы с заголовками `Origin` и `Access-Control-Request-Method`.
Это любые `OPTIONS`-запросы с заголовками `Origin` и `Access-Control-Request-Method`.
В этом случае middleware перехватит входящий запрос и отправит соответствующие CORS-заголовки в ответе, а также ответ `200` или `400` в информационных целях.
### Простые запросы
### Простые запросы { #simple-requests }
Любые запросы с заголовком `Origin`. В этом случае middleware передаст запрос дальше как обычно, но добавит соответствующие CORS-заголовки к ответу.
## Больше информации
## Больше информации { #more-info }
Для получения более подробной информации о <abbr title="Cross-Origin Resource Sharing">CORS</abbr>, обратитесь к <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" class="external-link" target="_blank">Документации CORS от Mozilla</a>.
Для получения более подробной информации о <abbr title="Cross-Origin Resource Sharing совместное использование ресурсов между источниками">CORS</abbr> обратитесь к <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" class="external-link" target="_blank">документации CORS от Mozilla</a>.
/// note | Технические детали

View File

@@ -1,14 +1,14 @@
# Отладка
# Отладка { #debugging }
Вы можете подключить отладчик в своем редакторе, например, в Visual Studio Code или PyCharm.
## Вызов `uvicorn`
## Вызов `uvicorn` { #call-uvicorn }
В вашем FastAPI приложении, импортируйте и вызовите `uvicorn` напрямую:
{* ../../docs_src/debugging/tutorial001.py hl[1,15] *}
### Описание `__name__ == "__main__"`
### Описание `__name__ == "__main__"` { #about-name-main }
Главная цель использования `__name__ == "__main__"` в том, чтобы код выполнялся при запуске файла с помощью:
@@ -26,7 +26,7 @@ $ python myapp.py
from myapp import app
```
#### Больше деталей
#### Больше деталей { #more-details }
Давайте назовём ваш файл `myapp.py`.
@@ -78,7 +78,7 @@ from myapp import app
///
## Запуск вашего кода с помощью отладчика
## Запуск вашего кода с помощью отладчика { #run-your-code-with-your-debugger }
Так как вы запускаете сервер Uvicorn непосредственно из вашего кода, вы можете вызвать Python программу (ваше FastAPI приложение) напрямую из отладчика.

View File

@@ -1,28 +1,30 @@
# Классы как зависимости
# Классы как зависимости { #classes-as-dependencies }
Прежде чем углубиться в систему **Внедрения Зависимостей**, давайте обновим предыдущий пример.
## `Словарь` из предыдущего примера
## `dict` из предыдущего примера { #a-dict-from-the-previous-example }
В предыдущем примере мы возвращали `словарь` из нашей зависимости:
В предыдущем примере мы возвращали `dict` из нашей зависимости:
{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[9] *}
Но затем мы получаем `словарь` в параметре `commons` *функции операции пути*. И мы знаем, что редакторы не могут обеспечить достаточную поддержку для `словаря`, поскольку они не могут знать их ключи и типы значений.
Но затем мы получаем `dict` в параметре `commons` *функции-обработчика пути*.
И мы знаем, что редакторы кода не могут обеспечить достаточную поддержку (например, автозавершение) для `dict`, поскольку они не могут знать их ключи и типы значений.
Мы можем сделать лучше...
## Что делает зависимость
## Что делает зависимость { #what-makes-a-dependency }
До сих пор вы видели зависимости, объявленные как функции.
Но это не единственный способ объявления зависимостей (хотя, вероятно, более распространенный).
Но это не единственный способ объявления зависимостей (хотя он, вероятно, более распространенный).
Ключевым фактором является то, что зависимость должна быть "вызываемой".
Ключевым фактором является то, что зависимость должна быть «вызываемой».
В Python "**вызываемый**" - это все, что Python может "вызвать", как функцию.
В Python «**вызываемый**» — это всё, что Python может «вызвать», как функцию.
Так, если у вас есть объект `something` (который может _не_ быть функцией) и вы можете "вызвать" его (выполнить) как:
Так, если у вас есть объект `something` (который может и _не_ быть функцией) и вы можете «вызвать» его (выполнить) так:
```Python
something()
@@ -34,9 +36,9 @@ something()
something(some_argument, some_keyword_argument="foo")
```
в таком случае он является "вызываемым".
в таком случае он является «вызываемым».
## Классы как зависимости
## Классы как зависимости { #classes-as-dependencies_1 }
Вы можете заметить, что для создания экземпляра класса в Python используется тот же синтаксис.
@@ -53,17 +55,17 @@ fluffy = Cat(name="Mr Fluffy")
В данном случае `fluffy` является экземпляром класса `Cat`.
А чтобы создать `fluffy`, вы "вызываете" `Cat`.
А чтобы создать `fluffy`, вы «вызываете» `Cat`.
Таким образом, класс в Python также является **вызываемым**.
Тогда в **FastAPI** в качестве зависимости можно использовать класс Python.
На самом деле FastAPI проверяет, что переданный объект является "вызываемым" (функция, класс или что-либо еще) и указаны необходимые для его вызова параметры.
На самом деле FastAPI проверяет, что переданный объект является «вызываемым» (функция, класс или что-либо еще) и какие параметры у него определены.
Если вы передаёте что-то, что можно "вызывать" в качестве зависимости в **FastAPI**, то он будет анализировать параметры, необходимые для "вызова" этого объекта и обрабатывать их так же, как параметры *функции операции пути*. Включая подзависимости.
Если вы передаёте «вызываемый» объект в качестве зависимости в **FastAPI**, он проанализирует параметры, необходимые для этого «вызываемого» объекта, и обработает их так же, как параметры *функции-обработчика пути*. Включая подзависимости.
Это относится и к вызываемым объектам без параметров. Работа с ними происходит точно так же, как и для *функций операции пути* без параметров.
Это относится и к вызываемым объектам без параметров. Работа с ними происходит точно так же, как и для *функций-обработчиков пути* без параметров.
Теперь мы можем изменить зависимость `common_parameters`, указанную выше, на класс `CommonQueryParams`:
@@ -73,37 +75,45 @@ fluffy = Cat(name="Mr Fluffy")
{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[12] *}
...имеет те же параметры, что и ранее используемая функция `common_parameters`:
...он имеет те же параметры, что и ранее используемая функция `common_parameters`:
{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[8] *}
Эти параметры и будут использоваться **FastAPI** для "решения" зависимости.
Эти параметры и будут использоваться **FastAPI** для «решения» зависимости.
В обоих случаях она будет иметь:
* Необязательный параметр запроса `q`, представляющий собой `str`.
* Параметр запроса `skip`, представляющий собой `int`, по умолчанию `0`.
* Параметр запроса `limit`, представляющий собой `int`, по умолчанию равный `100`.
* Параметр запроса `limit`, представляющий собой `int`, по умолчанию `100`.
В обоих случаях данные будут конвертированы, валидированы, документированы по схеме OpenAPI и т.д.
В обоих случаях данные будут конвертированы, валидированы, задокументированы в схеме OpenAPI и т.д.
## Как это использовать
## Как это использовать { #use-it }
Теперь вы можете объявить свою зависимость, используя этот класс.
{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[19] *}
**FastAPI** вызывает класс `CommonQueryParams`. При этом создается "экземпляр" этого класса, который будет передан в качестве параметра `commons` в вашу функцию.
**FastAPI** вызывает класс `CommonQueryParams`. При этом создается «экземпляр» этого класса, который будет передан в качестве параметра `commons` в вашу функцию.
## Аннотация типа или `Depends`
## Аннотация типа и `Depends` { #type-annotation-vs-depends }
Обратите внимание, что в приведенном выше коде мы два раза пишем `CommonQueryParams`:
//// tab | Python 3.6+ без Annotated
//// tab | Python 3.8+
```Python
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
```
////
//// tab | Python 3.8+ non-Annotated
/// tip | Подсказка
Рекомендуется использовать версию с `Annotated` если возможно.
Рекомендуется использовать версию с `Annotated`, если возможно.
///
@@ -113,15 +123,7 @@ commons: CommonQueryParams = Depends(CommonQueryParams)
////
//// tab | Python 3.6+
```Python
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
```
////
Последний параметр `CommonQueryParams`, в:
Последний `CommonQueryParams`, в:
```Python
... Depends(CommonQueryParams)
@@ -129,13 +131,13 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
...это то, что **FastAPI** будет использовать, чтобы узнать, что является зависимостью.
Из него FastAPI извлечёт объявленные параметры и именно их будет вызывать.
Из него FastAPI извлечёт объявленные параметры, и именно его FastAPI будет вызывать.
---
В этом случае первый `CommonQueryParams`, в:
//// tab | Python 3.6+
//// tab | Python 3.8+
```Python
commons: Annotated[CommonQueryParams, ...
@@ -143,11 +145,11 @@ commons: Annotated[CommonQueryParams, ...
////
//// tab | Python 3.6+ без Annotated
//// tab | Python 3.8+ non-Annotated
/// tip | Подсказка
Рекомендуется использовать версию с `Annotated` если возможно.
Рекомендуется использовать версию с `Annotated`, если возможно.
///
@@ -161,7 +163,7 @@ commons: CommonQueryParams ...
На самом деле можно написать просто:
//// tab | Python 3.6+
//// tab | Python 3.8+
```Python
commons: Annotated[Any, Depends(CommonQueryParams)]
@@ -169,11 +171,11 @@ commons: Annotated[Any, Depends(CommonQueryParams)]
////
//// tab | Python 3.6+ без Annotated
//// tab | Python 3.8+ non-Annotated
/// tip | Подсказка
Рекомендуется использовать версию с `Annotated` если возможно.
Рекомендуется использовать версию с `Annotated`, если возможно.
///
@@ -187,19 +189,27 @@ commons = Depends(CommonQueryParams)
{* ../../docs_src/dependencies/tutorial003_an_py310.py hl[19] *}
Но объявление типа приветствуется, так как в этом случае ваш редактор будет знать, что будет передано в качестве параметра `commons`, и тогда он сможет помочь вам с автодополнением, проверкой типов и т.д:
Но объявление типа приветствуется, так как в этом случае ваш редактор кода будет знать, что будет передано в качестве параметра `commons`, и тогда он сможет помочь вам с автозавершением, проверкой типов и т.д.:
<img src="/img/tutorial/dependencies/image02.png">
## Сокращение
## Сокращение { #shortcut }
Но вы видите, что здесь мы имеем некоторое повторение кода, дважды написав `CommonQueryParams`:
//// tab | Python 3.6+ без Annotated
//// tab | Python 3.8+
```Python
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
```
////
//// tab | Python 3.8+ non-Annotated
/// tip | Подсказка
Рекомендуется использовать версию с `Annotated` если возможно.
Рекомендуется использовать версию с `Annotated`, если возможно.
///
@@ -209,20 +219,13 @@ commons: CommonQueryParams = Depends(CommonQueryParams)
////
//// tab | Python 3.6+
```Python
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
```
////
Для случаев, когда зависимостью является *конкретный* класс, который **FastAPI** "вызовет" для создания экземпляра этого класса, можно использовать укороченную запись.
**FastAPI** предоставляет сокращение для таких случаев, когда зависимость — это *конкретный* класс, который **FastAPI** будет «вызывать» для создания экземпляра этого класса.
Для этих конкретных случаев вы можете сделать следующее.
Вместо того чтобы писать:
//// tab | Python 3.6+
//// tab | Python 3.8+
```Python
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
@@ -230,11 +233,11 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
////
//// tab | Python 3.6+ без Annotated
//// tab | Python 3.8+ non-Annotated
/// tip | Подсказка
Рекомендуется использовать версию с `Annotated` если возможно.
Рекомендуется использовать версию с `Annotated`, если возможно.
///
@@ -246,7 +249,7 @@ commons: CommonQueryParams = Depends(CommonQueryParams)
...следует написать:
//// tab | Python 3.6+
//// tab | Python 3.8+
```Python
commons: Annotated[CommonQueryParams, Depends()]
@@ -254,11 +257,11 @@ commons: Annotated[CommonQueryParams, Depends()]
////
//// tab | Python 3.6 без Annotated
//// tab | Python 3.8 non-Annotated
/// tip | Подсказка
Рекомендуется использовать версию с `Annotated` если возможно.
Рекомендуется использовать версию с `Annotated`, если возможно.
///
@@ -278,7 +281,7 @@ commons: CommonQueryParams = Depends()
/// tip | Подсказка
Если это покажется вам более запутанным, чем полезным, не обращайте внимания, это вам не *нужно*.
Если это покажется вам более запутанным, чем полезным, не обращайте внимания это вам не *нужно*.
Это просто сокращение. Потому что **FastAPI** заботится о том, чтобы помочь вам свести к минимуму повторение кода.

View File

@@ -1,4 +1,4 @@
# Зависимости в декораторах операции пути
# Зависимости в декораторах операции пути { #dependencies-in-path-operation-decorators }
В некоторых случаях, возвращаемое значение зависимости не используется внутри *функции операции пути*.
@@ -8,7 +8,7 @@
Для таких ситуаций, вместо объявления *функции операции пути* с параметром `Depends`, вы можете добавить список зависимостей `dependencies` в *декоратор операции пути*.
## Добавление `dependencies` в *декоратор операции пути*
## Добавление `dependencies` (зависимостей) в *декоратор операции пути* { #add-dependencies-to-the-path-operation-decorator }
*Декоратор операции пути* получает необязательный аргумент `dependencies`.
@@ -36,23 +36,23 @@
///
## Исключения в dependencies и возвращаемые значения
## Исключения в Зависимостях и возвращаемые значения { #dependencies-errors-and-return-values }
Вы можете использовать те же *функции* зависимостей, что и обычно.
### Требования к зависимостям
### Требования к зависимостям { #dependency-requirements }
Они могут объявлять требования к запросу (например заголовки) или другие подзависимости:
{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[8,13] *}
### Вызов исключений
### Вызов исключений { #raise-exceptions }
Зависимости из dependencies могут вызывать исключения с помощью `raise`, как и обычные зависимости:
{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[10,15] *}
### Возвращаемые значения
### Возвращаемые значения { #return-values }
И они могут возвращать значения или нет, эти значения использоваться не будут.
@@ -60,10 +60,10 @@
{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[11,16] *}
## Dependencies для группы *операций путей*
## Зависимости для группы *операций путей* { #dependencies-for-a-group-of-path-operations }
Позже, читая о том как структурировать большие приложения ([Bigger Applications - Multiple Files](../../tutorial/bigger-applications.md){.internal-link target=_blank}), возможно, многофайловые, вы узнаете как объявить единый параметр `dependencies` для всей группы *операций путей*.
Позже, читая о том как структурировать большие приложения ([Большие приложения — несколько файлов](../../tutorial/bigger-applications.md){.internal-link target=_blank}), возможно, многофайловые, вы узнаете как объявить единый параметр `dependencies` для всей группы *операций путей*.
## Глобальный Dependencies
## Глобальные Зависимости { #global-dependencies }
Далее мы увидим, как можно добавить dependencies для всего `FastAPI` приложения, так чтобы они применялись к каждой *операции пути*.

View File

@@ -1,125 +1,139 @@
# Зависимости с yield
# Зависимости с yield { #dependencies-with-yield }
FastAPI поддерживает зависимости, которые выполняют некоторые <abbr title='также известные как "exit", "cleanup", "teardown", "close", "context managers", ...'>дополнительные действия после завершения работы</abbr>.
FastAPI поддерживает зависимости, которые выполняют некоторые <abbr title='иногда также называемые "exit code", "cleanup code", "teardown code", "closing code", "context manager exit code" и т.п.'>дополнительные шаги после завершения</abbr>.
Для этого используйте `yield` вместо `return`, а дополнительный код напишите после него.
Для этого используйте `yield` вместо `return`, а дополнительные шаги (код) напишите после него.
/// tip | Подсказка
Обязательно используйте `yield` один-единственный раз.
Убедитесь, что используете `yield` только один раз на одну зависимость.
///
/// note | Технические детали
Любая функция, с которой может работать:
Любая функция, с которой можно корректно использовать:
* <a href="https://docs.python.org/3/library/contextlib.html#contextlib.contextmanager" class="external-link" target="_blank">`@contextlib.contextmanager`</a> или
* <a href="https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager" class="external-link" target="_blank">`@contextlib.asynccontextmanager`</a>
будет корректно использоваться в качестве **FastAPI**-зависимости.
будет корректной для использования в качестве зависимости **FastAPI**.
На самом деле, FastAPI использует эту пару декораторов "под капотом".
На самом деле, FastAPI использует эти два декоратора внутренне.
///
## Зависимость базы данных с помощью `yield`
## Зависимость базы данных с помощью `yield` { #a-database-dependency-with-yield }
Например, с его помощью можно создать сессию работы с базой данных и закрыть его после завершения.
Например, с его помощью можно создать сессию работы с базой данных и закрыть её после завершения.
Перед созданием ответа будет выполнен только код до и включая `yield`.
Перед созданием ответа будет выполнен только код до и включая оператор `yield`:
{* ../../docs_src/dependencies/tutorial007.py hl[2:4] *}
Полученное значение и есть то, что будет внедрено в функцию операции пути и другие зависимости:
Значение, полученное из `yield`, внедряется в *операции пути* и другие зависимости:
{* ../../docs_src/dependencies/tutorial007.py hl[4] *}
Код, следующий за оператором `yield`, выполняется после доставки ответа:
Код, следующий за оператором `yield`, выполняется после ответа:
{* ../../docs_src/dependencies/tutorial007.py hl[5:6] *}
/// tip | Подсказка
Можно использовать как `async` так и обычные функции.
Можно использовать как `async`, так и обычные функции.
**FastAPI** это корректно обработает, и в обоих случаях будет делать то же самое, что и с обычными зависимостями.
**FastAPI** корректно обработает каждый вариант, так же как и с обычными зависимостями.
///
## Зависимость с `yield` и `try` одновременно
## Зависимость с `yield` и `try` { #a-dependency-with-yield-and-try }
Если использовать блок `try` в зависимости с `yield`, то будет получено всякое исключение, которое было выброшено при использовании зависимости.
Если использовать блок `try` в зависимости с `yield`, то вы получите любое исключение, которое было выброшено при использовании зависимости.
Например, если какой-то код в какой-то момент в середине, в другой зависимости или в *функции операции пути*, сделал "откат" транзакции базы данных или создал любую другую ошибку, то вы получите исключение в своей зависимости.
Например, если какой-то код в какой-то момент в середине, в другой зависимости или в *операции пути*, сделал "откат" транзакции базы данных или создал любую другую ошибку, то вы получите это исключение в своей зависимости.
Таким образом, можно искать конкретное исключение внутри зависимости с помощью `except SomeException`.
Таким же образом можно использовать `finally`, чтобы убедиться, что обязательные шаги при выходе выполнены, независимо от того, было ли исключение или нет.
Точно так же можно использовать `finally`, чтобы убедиться, что обязательные шаги при выходе выполнены независимо от того, было ли исключение или нет.
{* ../../docs_src/dependencies/tutorial007.py hl[3,5] *}
## Подзависимости с `yield`
## Подзависимости с `yield` { #sub-dependencies-with-yield }
Вы можете иметь подзависимости и "деревья" подзависимостей любого размера и формы, и любая из них или все они могут использовать `yield`.
**FastAPI** будет следить за тем, чтобы "код по выходу" в каждой зависимости с `yield` выполнялся в правильном порядке.
**FastAPI** проследит за тем, чтобы «код выхода» в каждой зависимости с `yield` выполнялся в правильном порядке.
Например, `dependency_c` может иметь зависимость от `dependency_b`, а `dependency_b` от `dependency_a`:
Например, `dependency_c` может зависеть от `dependency_b`, а `dependency_b` от `dependency_a`:
{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[6,14,22] *}
И все они могут использовать `yield`.
В этом случае `dependency_c` для выполнения своего кода выхода нуждается в том, чтобы значение из `dependency_b` (здесь `dep_b`) было еще доступно.
В этом случае `dependency_c` для выполнения своего кода выхода нуждается в том, чтобы значение из `dependency_b` (здесь `dep_b`) всё ещё было доступно.
И, в свою очередь, `dependency_b` нуждается в том, чтобы значение из `dependency_a` (здесь `dep_a`) было доступно для ее завершающего кода.
И, в свою очередь, `dependency_b` нуждается в том, чтобы значение из `dependency_a` (здесь `dep_a`) было доступно для её кода выхода.
{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[18:19,26:27] *}
Точно так же можно иметь часть зависимостей с `yield`, часть с `return`, и какие-то из них могут зависеть друг от друга.
Точно так же можно иметь часть зависимостей с `yield`, часть с `return`, и какие-то из них могут зависеть друг от друга.
Либо у вас может быть одна зависимость, которая требует несколько других зависимостей с `yield` и т.д.
Комбинации зависимостей могут быть какими вам угодно.
Комбинации зависимостей могут быть какими угодно.
**FastAPI** проследит за тем, чтобы все выполнялось в правильном порядке.
**FastAPI** проследит за тем, чтобы всё выполнялось в правильном порядке.
/// note | Технические детали
Это работает благодаря <a href="https://docs.python.org/3/library/contextlib.html" class="external-link" target="_blank">Контекстным менеджерам</a> в Python.
Это работает благодаря <a href="https://docs.python.org/3/library/contextlib.html" class="external-link" target="_blank">менеджерам контекста</a> в Python.
**FastAPI** использует их внутренне для достижения этого.
///
**FastAPI** использует их "под капотом" с этой целью.
## Зависимости с `yield` и `HTTPException` { #dependencies-with-yield-and-httpexception }
## Зависимости с `yield` и `HTTPException`
Вы видели, что можно использовать зависимости с `yield` и иметь блоки `try`, которые пытаются выполнить некоторый код, а затем запускают код выхода в `finally`.
Вы видели, что можно использовать зависимости с `yield` совместно с блоком `try`, отлавливающие исключения.
Также вы можете использовать `except`, чтобы поймать вызванное исключение и что-то с ним сделать.
Таким же образом вы можете поднять исключение `HTTPException` или что-то подобное в завершающем коде, после `yield`.
Код выхода в зависимостях с `yield` выполняется *после* отправки ответа, поэтому [Обработчик исключений](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} уже будет запущен. В коде выхода (после `yield`) нет ничего, перехватывающего исключения, брошенные вашими зависимостями.
Таким образом, если после `yield` возникает `HTTPException`, то стандартный (или любой пользовательский) обработчик исключений, который перехватывает `HTTPException` и возвращает ответ HTTP 400, уже не сможет перехватить это исключение.
Благодаря этому все, что установлено в зависимости (например, сеанс работы с БД), может быть использовано, например, фоновыми задачами.
Фоновые задачи выполняются *после* отправки ответа. Поэтому нет возможности поднять `HTTPException`, так как нет даже возможности изменить уже отправленный ответ.
Но если фоновая задача создает ошибку в БД, то, по крайней мере, можно сделать откат или чисто закрыть сессию в зависимости с помощью `yield`, а также, возможно, занести ошибку в журнал или сообщить о ней в удаленную систему отслеживания.
Если у вас есть код, который, как вы знаете, может вызвать исключение, сделайте самую обычную/"питонячью" вещь и добавьте блок `try` в этот участок кода.
Если у вас есть пользовательские исключения, которые вы хотите обрабатывать *до* возврата ответа и, возможно, модифицировать ответ, даже вызывая `HTTPException`, создайте [Cобственный обработчик исключений](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}.
Например, вы можете <abbr title="«raise» дословно - «поднять», но «вызвать», «сгенерировать» или «выбросить» употребляется чаще">вызвать</abbr> другое исключение, например `HTTPException`.
/// tip | Подсказка
Вы все еще можете вызывать исключения, включая `HTTPException`, *до* `yield`. Но не после.
Это довольно продвинутая техника, и в большинстве случаев она вам не понадобится, так как вы можете вызывать исключения (включая `HTTPException`) в остальном коде вашего приложения, например, в *функции-обработчике пути*.
Но если понадобится — возможность есть. 🤓
///
Последовательность выполнения примерно такая, как на этой схеме. Время течет сверху вниз. А каждый столбец - это одна из частей, взаимодействующих с кодом или выполняющих код.
{* ../../docs_src/dependencies/tutorial008b_an_py39.py hl[18:22,31] *}
Если вы хотите перехватывать исключения и формировать на их основе пользовательский ответ, создайте [Пользовательский обработчик исключений](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}.
## Зависимости с `yield` и `except` { #dependencies-with-yield-and-except }
Если вы ловите исключение с помощью `except` в зависимости с `yield` и не вызываете его снова (или не вызываете новое исключение), FastAPI не сможет заметить, что было исключение — так же, как это происходит в обычном Python:
{* ../../docs_src/dependencies/tutorial008c_an_py39.py hl[15:16] *}
В этом случае клиент получит *HTTP 500 Internal Server Error*, как и должно быть, поскольку мы не вызываем `HTTPException` или что-то подобное, но на сервере **не будет никаких логов** или других указаний на то, какая была ошибка. 😱
### Всегда делайте `raise` в зависимостях с `yield` и `except` { #always-raise-in-dependencies-with-yield-and-except }
Если вы ловите исключение в зависимости с `yield`, то, если вы не вызываете другой `HTTPException` или что-то подобное, вам следует повторно вызвать исходное исключение.
Вы можете повторно вызвать то же самое исключение с помощью `raise`:
{* ../../docs_src/dependencies/tutorial008d_an_py39.py hl[17] *}
Теперь клиент получит тот же *HTTP 500 Internal Server Error*, но на сервере в логах будет наше пользовательское `InternalError`. 😎
## Выполнение зависимостей с `yield` { #execution-of-dependencies-with-yield }
Последовательность выполнения примерно такая, как на этой схеме. Время течёт сверху вниз. А каждый столбец — это одна из частей, взаимодействующих с кодом или выполняющих код.
```mermaid
sequenceDiagram
@@ -130,85 +144,56 @@ participant dep as Dep with yield
participant operation as Path Operation
participant tasks as Background tasks
Note over client,tasks: Can raise exception for dependency, handled after response is sent
Note over client,operation: Can raise HTTPException and can change the response
Note over client,operation: Can raise exceptions, including HTTPException
client ->> dep: Start request
Note over dep: Run code up to yield
opt raise
dep -->> handler: Raise HTTPException
opt raise Exception
dep -->> handler: Raise Exception
handler -->> client: HTTP error response
dep -->> dep: Raise other exception
end
dep ->> operation: Run dependency, e.g. DB session
opt raise
operation -->> dep: Raise HTTPException
dep -->> handler: Auto forward exception
operation -->> dep: Raise Exception (e.g. HTTPException)
opt handle
dep -->> dep: Can catch exception, raise a new HTTPException, raise other exception
end
handler -->> client: HTTP error response
operation -->> dep: Raise other exception
dep -->> handler: Auto forward exception
end
operation ->> client: Return response to client
Note over client,operation: Response is already sent, can't change it anymore
opt Tasks
operation -->> tasks: Send background tasks
end
opt Raise other exception
tasks -->> dep: Raise other exception
end
Note over dep: After yield
opt Handle other exception
dep -->> dep: Handle exception, can't change response. E.g. close DB session.
tasks -->> tasks: Handle exceptions in the background task code
end
```
/// info | Дополнительная информация
Клиенту будет отправлен только **один ответ**. Это может быть один из ответов об ошибке или это будет ответ от *операции пути*.
Клиенту будет отправлен только **один ответ**. Это может быть один из ответов об ошибке или ответ от *операции пути*.
После отправки одного из этих ответов никакой другой ответ не может быть отправлен.
После отправки одного из этих ответов никакой другой ответ отправить нельзя.
///
/// tip | Подсказка
На этой диаграмме показано "HttpException", но вы также можете вызвать любое другое исключение, для которого вы создаете [Пользовательский обработчик исключений](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}.
Если вы создадите какое-либо исключение, оно будет передано зависимостям с yield, включая `HttpException`, а затем **снова** обработчикам исключений. Если для этого исключения нет обработчика исключений, то оно будет обработано внутренним "ServerErrorMiddleware" по умолчанию, возвращающим код состояния HTTP 500, чтобы уведомить клиента, что на сервере произошла ошибка.
Если вы вызовете какое-либо исключение в коде из *функции-обработчика пути*, оно будет передано зависимостям с `yield`, включая `HTTPException`. В большинстве случаев вы захотите повторно вызвать то же самое исключение или новое из зависимости с `yield`, чтобы убедиться, что оно корректно обработано.
///
## Зависимости с `yield`, `HTTPException` и фоновыми задачами
## Зависимости с `yield`, `HTTPException`, `except` и фоновыми задачами { #dependencies-with-yield-httpexception-except-and-background-tasks }
/// warning | Внимание
Зависимости с `yield` со временем эволюционировали, чтобы покрыть разные сценарии и исправить некоторые проблемы.
Скорее всего, вам не нужны эти технические подробности, вы можете пропустить этот раздел и продолжить ниже.
Если вы хотите посмотреть, что менялось в разных версиях FastAPI, вы можете прочитать об этом подробнее в продвинутом руководстве: [Продвинутые зависимости — зависимости с `yield`, `HTTPException`, `except` и фоновыми задачами](../../advanced/advanced-dependencies.md#dependencies-with-yield-httpexception-except-and-background-tasks){.internal-link target=_blank}.
## Контекстные менеджеры { #context-managers }
Эти подробности полезны, главным образом, если вы использовали версию FastAPI до 0.106.0 и использовали ресурсы из зависимостей с `yield` в фоновых задачах.
### Что такое «контекстные менеджеры» { #what-are-context-managers }
///
До версии FastAPI 0.106.0 вызывать исключения после `yield` было невозможно, код выхода в зависимостях с `yield` выполнялся *после* отправки ответа, поэтому [Обработчик Ошибок](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} уже был бы запущен.
Это было сделано главным образом для того, чтобы позволить использовать те же объекты, "отданные" зависимостями, внутри фоновых задач, поскольку код выхода будет выполняться после завершения фоновых задач.
Тем не менее, поскольку это означало бы ожидание ответа в сети, а также ненужное удержание ресурса в зависимости от доходности (например, соединение с базой данных), это было изменено в FastAPI 0.106.0.
/// tip | Подсказка
Кроме того, фоновая задача обычно представляет собой независимый набор логики, который должен обрабатываться отдельно, со своими собственными ресурсами (например, собственным подключением к базе данных).
Таким образом, вы, вероятно, получите более чистый код.
///
Если вы полагались на это поведение, то теперь вам следует создавать ресурсы для фоновых задач внутри самой фоновой задачи, а внутри использовать только те данные, которые не зависят от ресурсов зависимостей с `yield`.
Например, вместо того чтобы использовать ту же сессию базы данных, вы создадите новую сессию базы данных внутри фоновой задачи и будете получать объекты из базы данных с помощью этой новой сессии. А затем, вместо того чтобы передавать объект из базы данных в качестве параметра в функцию фоновой задачи, вы передадите идентификатор этого объекта, а затем снова получите объект в функции фоновой задачи.
## Контекстные менеджеры
### Что такое "контекстные менеджеры"
"Контекстные менеджеры" - это любые объекты Python, которые можно использовать в операторе `with`.
«Контекстные менеджеры» — это любые объекты Python, которые можно использовать в операторе `with`.
Например, <a href="https://docs.python.org/3/tutorial/inputoutput.html#reading-and-writing-files" class="external-link" target="_blank">можно использовать `with` для чтения файла</a>:
@@ -218,41 +203,41 @@ with open("./somefile.txt") as f:
print(contents)
```
Под капотом" open("./somefile.txt") создаёт объект называемый "контекстным менеджером".
Под капотом вызов `open("./somefile.txt")` создаёт объект, называемый «контекстным менеджером».
Когда блок `with` завершается, он обязательно закрывает файл, даже если были исключения.
Когда вы создаете зависимость с помощью `yield`, **FastAPI** внутренне преобразует ее в контекстный менеджер и объединяет с некоторыми другими связанными инструментами.
Когда вы создаёте зависимость с `yield`, **FastAPI** внутренне создаёт для неё менеджер контекста и сочетает его с некоторыми другими связанными инструментами.
### Использование менеджеров контекста в зависимостях с помощью `yield`
### Использование менеджеров контекста в зависимостях с `yield` { #using-context-managers-in-dependencies-with-yield }
/// warning | Внимание
Это более или менее "продвинутая" идея.
Это, более или менее, «продвинутая» идея.
Если вы только начинаете работать с **FastAPI**, то лучше пока пропустить этот пункт.
///
В Python для создания менеджеров контекста можно <a href="https://docs.python.org/3/reference/datamodel.html#context-managers" class="external-link" target="_blank">создать класс с двумя методами: `__enter__()` и `__exit__()`</a>.
В Python можно создавать менеджеры контекста, <a href="https://docs.python.org/3/reference/datamodel.html#context-managers" class="external-link" target="_blank">создав класс с двумя методами: `__enter__()` и `__exit__()`</a>.
Вы также можете использовать их внутри зависимостей **FastAPI** с `yield`, используя операторы
Их также можно использовать внутри зависимостей **FastAPI** с `yield`, применяя операторы
`with` или `async with` внутри функции зависимости:
{* ../../docs_src/dependencies/tutorial010.py hl[1:9,13] *}
/// tip | Подсказка
Другой способ создания контекстного менеджера - с помощью:
Другой способ создания менеджера контекста с помощью:
* <a href="https://docs.python.org/3/library/contextlib.html#contextlib.contextmanager" class="external-link" target="_blank">`@contextlib.contextmanager`</a> или
* <a href="https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager" class="external-link" target="_blank">`@contextlib.asynccontextmanager`</a>
используйте их для оформления функции с одним `yield`.
оформив ими функцию с одним `yield`.
Это то, что **FastAPI** использует внутри себя для зависимостей с `yield`.
Именно это **FastAPI** использует внутренне для зависимостей с `yield`.
Но использовать декораторы для зависимостей FastAPI не обязательно (да и не стоит).
Но использовать эти декораторы для зависимостей FastAPI не обязательно (и не стоит).
FastAPI сделает это за вас на внутреннем уровне.

View File

@@ -1,15 +1,15 @@
# Глобальные зависимости
# Глобальные зависимости { #global-dependencies }
Для некоторых типов приложений может потребоваться добавить зависимости ко всему приложению.
Подобно тому, как вы можете [добавлять зависимости через параметр `dependencies` в *декораторах операций пути*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, вы можете добавлять зависимости сразу ко всему `FastAPI` приложению.
Подобно тому, как вы можете [добавлять `dependencies` (зависимости) в *декораторах операций пути*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, вы можете добавлять зависимости сразу ко всему `FastAPI` приложению.
В этом случае они будут применяться ко всем *операциям пути* в приложении:
{* ../../docs_src/dependencies/tutorial012_an_py39.py hl[16] *}
Все способы [добавления зависимостей в *декораторах операций пути*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} по-прежнему применимы, но в данном случае зависимости применяются ко всем *операциям пути* приложения.
Все способы [добавления `dependencies` (зависимостей) в *декораторах операций пути*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} по-прежнему применимы, но в данном случае зависимости применяются ко всем *операциям пути* приложения.
## Зависимости для групп *операций пути*
## Зависимости для групп *операций пути* { #dependencies-for-groups-of-path-operations }
Позднее, читая о том, как структурировать более крупные [приложения, содержащие много файлов](../../tutorial/bigger-applications.md){.internal-link target=_blank}, вы узнаете, как объявить один параметр dependencies для целой группы *операций пути*.
Позднее, читая о том, как структурировать более крупные [приложения, содержащие много файлов](../../tutorial/bigger-applications.md){.internal-link target=_blank}, вы узнаете, как объявить один параметр `dependencies` для целой группы *операций пути*.

View File

@@ -1,91 +1,97 @@
# Зависимости
# Зависимости { #dependencies }
**FastAPI** имеет очень мощную и интуитивную систему **<abbr title="also known as components, resources, providers, services, injectables">Dependency Injection</abbr>**.
**FastAPI** имеет очень мощную, но интуитивную систему **<abbr title="также известно как: компоненты, ресурсы, провайдеры, сервисы, внедряемые зависимости">Инъекция зависимостей</abbr>**.
Она проектировалась таким образом, чтобы быть простой в использовании и облегчить любому разработчику интеграцию других компонентов с **FastAPI**.
Она спроектирована так, чтобы быть очень простой в использовании и облегчать любому разработчику интеграцию других компонентов с **FastAPI**.
## Что такое "Dependency Injection" (инъекция зависимости)
## Что такое инъекция зависимостей («Dependency Injection») { #what-is-dependency-injection }
**"Dependency Injection"** в программировании означает, что у вашего кода (в данном случае, вашей *функции обработки пути*) есть способы объявить вещи, которые запрашиваются для работы и использования: "зависимости".
В программировании **«Dependency Injection»** означает, что у вашего кода (в данном случае у ваших *функций обработки пути*) есть способ объявить вещи, которые требуются для его работы и использования: «зависимости».
И потом эта система (в нашем случае **FastAPI**) организует всё, что требуется, чтобы обеспечить ваш код этой зависимостью (сделать "инъекцию" зависимости).
И затем эта система (в нашем случае **FastAPI**) позаботится о том, чтобы сделать всё необходимое для предоставления вашему коду этих зависимостей (сделать «инъекцию» зависимостей).
Это очень полезно, когда вам нужно:
* Обеспечить общую логику (один и тот же алгоритм снова и снова).
* Общее соединение с базой данных.
* Обеспечение безопасности, аутентификации, запроса роли и т.п.
* И многое другое.
* Разделять соединения с базой данных.
* Обеспечить безопасность, аутентификацию, требования к ролям и т. п.
* И многое другое...
Всё это минимизирует повторение кода.
Всё это при минимизации повторения кода.
## Первые шаги
## Первые шаги { #first-steps }
Давайте рассмотрим очень простой пример. Он настолько простой, что на данный момент почти бесполезный.
Давайте рассмотрим очень простой пример. Он настолько простой, что пока не очень полезен.
Но таким способом мы можем сфокусироваться на том, как же всё таки работает система **Dependency Injection**.
Но так мы сможем сосредоточиться на том, как работает система **Dependency Injection**.
### Создание зависимости или "зависимого"
Давайте для начала сфокусируемся на зависимостях.
### Создайте зависимость, или «dependable» (от чего что-то зависит) { #create-a-dependency-or-dependable }
Сначала сосредоточимся на зависимости.
Это просто функция, которая может принимать те же параметры, что и *функция обработки пути*:
Это просто функция, которая может принимать все те же параметры, что и *функции обработки пути*:
{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[8:9] *}
**И всё.**
И всё.
**2 строки.**
И теперь она той же формы и структуры, что и все ваши *функции обработки пути*.
И она имеет ту же форму и структуру, что и все ваши *функции обработки пути*.
Вы можете думать об *функции обработки пути* как о функции без "декоратора" (без `@app.get("/some-path")`).
Можно думать о ней как о *функции обработки пути* без «декоратора» (без `@app.get("/some-path")`).
И она может возвращать всё, что требуется.
И она может возвращать что угодно.
В этом случае, эта зависимость ожидает:
В этом случае эта зависимость ожидает:
* Необязательный query-параметр `q` с типом `str`
* Необязательный query-параметр `skip` с типом `int`, и значением по умолчанию `0`
* Необязательный query-параметр `limit` с типом `int`, и значением по умолчанию `100`
* Необязательный query-параметр `q` типа `str`.
* Необязательный query-параметр `skip` типа `int`, по умолчанию `0`.
* Необязательный query-параметр `limit` типа `int`, по умолчанию `100`.
И в конце она возвращает `dict`, содержащий эти значения.
А затем просто возвращает `dict`, содержащий эти значения.
/// info | Информация
**FastAPI** добавил поддержку для `Annotated` (и начал её рекомендовать) в версии 0.95.0.
FastAPI добавил поддержку `Annotated` (и начал рекомендовать его использование) в версии 0.95.0.
Если у вас более старая версия, будут ошибки при попытке использовать `Annotated`.
Если у вас более старая версия, вы получите ошибки при попытке использовать `Annotated`.
Убедитесь, что вы [Обновили FastAPI версию](../../deployment/versions.md#fastapi_2){.internal-link target=_blank} до, как минимум 0.95.1, перед тем как использовать `Annotated`.
Убедитесь, что вы [обновили версию FastAPI](../../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} как минимум до 0.95.1, прежде чем использовать `Annotated`.
///
### Import `Depends`
### Импорт `Depends` { #import-depends }
{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[3] *}
### Объявите зависимость в "зависимом"
### Объявите зависимость в «зависимом» { #declare-the-dependency-in-the-dependant }
Точно так же, как вы использовали `Body`, `Query` и т.д. с вашей *функцией обработки пути* для параметров, используйте `Depends` с новым параметром:
Точно так же, как вы используете `Body`, `Query` и т. д. с параметрами вашей *функции обработки пути*, используйте `Depends` с новым параметром:
{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[13,18] *}
`Depends` работает немного иначе. Вы передаёте в `Depends` одиночный параметр, который будет похож на функцию.
Хотя вы используете `Depends` в параметрах вашей функции так же, как `Body`, `Query` и т. д., `Depends` работает немного иначе.
Вы **не вызываете его** на месте (не добавляете скобочки в конце: 👎 *your_best_func()*👎), просто передаёте как параметр в `Depends()`.
В `Depends` вы передаёте только один параметр.
И потом функция берёт параметры так же, как *функция обработки пути*.
Этот параметр должен быть чем-то вроде функции.
Вы **не вызываете её** напрямую (не добавляйте круглые скобки в конце), просто передаёте её как параметр в `Depends()`.
И эта функция принимает параметры так же, как *функции обработки пути*.
/// tip | Подсказка
В следующей главе вы увидите, какие другие вещи, помимо функций, можно использовать в качестве зависимостей.
В следующей главе вы увидите, какие ещё «вещи», помимо функций, можно использовать в качестве зависимостей.
///
Каждый раз, когда новый запрос приходит, **FastAPI** позаботится о:
Каждый раз, когда приходит новый запрос, **FastAPI** позаботится о:
* Вызове вашей зависимости ("зависимого") функции с корректными параметрами.
* Вызове вашей зависимости («dependable») с корректными параметрами.
* Получении результата из вашей функции.
* Назначении результата в параметр в вашей *функции обработки пути*.
* Присваивании этого результата параметру в вашей *функции обработки пути*.
```mermaid
graph TB
@@ -98,121 +104,121 @@ common_parameters --> read_items
common_parameters --> read_users
```
Таким образом, вы пишете общий код один раз, и **FastAPI** позаботится о его вызове для ваших *операций с путями*.
Таким образом, вы пишете общий код один раз, а **FastAPI** позаботится о его вызове для ваших *операций пути*.
/// check | Проверка
Обратите внимание, что вы не создаёте специальный класс и не передаёте его куда-то в **FastAPI** для регистрации, или что-то в этом роде.
Обратите внимание, что вам не нужно создавать специальный класс и передавать его куда-то в **FastAPI**, чтобы «зарегистрировать» его или что-то подобное.
Вы просто передаёте это в `Depends`, и **FastAPI** знает, что делать дальше.
Вы просто передаёте его в `Depends`, и **FastAPI** знает, что делать дальше.
///
## Объединяем с `Annotated` зависимостями
## Использование зависимости с `Annotated` в нескольких местах { #share-annotated-dependencies }
В приведенном выше примере есть небольшое **повторение кода**.
В приведённых выше примерах есть небольшое **повторение кода**.
Когда вам нужно использовать `common_parameters()` зависимость, вы должны написать весь параметр с аннотацией типов и `Depends()`:
Когда вам нужно использовать зависимость `common_parameters()`, вы должны написать весь параметр с аннотацией типа и `Depends()`:
```Python
commons: Annotated[dict, Depends(common_parameters)]
```
Но потому что мы используем `Annotated`, мы можем хранить `Annotated` значение в переменной и использовать его в нескольких местах:
Но поскольку мы используем `Annotated`, мы можем сохранить это значение `Annotated` в переменную и использовать его в нескольких местах:
{* ../../docs_src/dependencies/tutorial001_02_an_py310.py hl[12,16,21] *}
/// tip | Подсказка
Это стандартный синтаксис python и называется "type alias", это не особенность **FastAPI**.
Это стандартный Python, это называется «type alias», и это не особенность **FastAPI**.
Но потому что **FastAPI** базируется на стандартах Python, включая `Annotated`, вы можете использовать этот трюк в вашем коде. 😎
Но поскольку **FastAPI** основан на стандартах Python, включая `Annotated`, вы можете использовать этот трюк в своём коде. 😎
///
Зависимости продолжат работу как ожидалось, и **лучшая часть** в том, что **информация о типе будет сохранена**. Это означает, что ваш редактор кода будет корректно обрабатывать **автодополнения**, **встроенные ошибки** и так далее. То же самое относится и к инструментам, таким как `mypy`.
Зависимости продолжат работать как ожидалось, и **лучшая часть** в том, что **информация о типах будет сохранена**, а значит, ваш редактор кода продолжит предоставлять **автозавершение**, **встроенные ошибки** и т. То же относится и к другим инструментам, таким как `mypy`.
Это очень полезно, когда вы интегрируете это в **большую кодовую базу**, используя **одинаковые зависимости** снова и снова во **многих** ***операциях пути***.
Это особенно полезно, когда вы используете это в **большой кодовой базе**, где вы используете **одни и те же зависимости** снова и снова во **многих *операциях пути***.
## Использовать `async` или не `async`
## Использовать `async` или не `async` { #to-async-or-not-to-async }
Для зависимостей, вызванных **FastAPI** (то же самое, что и ваши *функции обработки пути*), те же правила, что приняты для определения ваших функций.
Поскольку зависимости также вызываются **FastAPI** (как и ваши *функции обработки пути*), применяются те же правила при определении ваших функций.
Вы можете использовать `async def` или обычное `def`.
Вы также можете объявить зависимости с `async def` внутри обычной `def` *функции обработки пути*, или `def` зависимости внутри `async def` *функции обработки пути*, и так далее.
И вы можете объявлять зависимости с `async def` внутри обычных *функций обработки пути* `def`, или зависимости `def` внутри *функций обработки пути* `async def` и т. д.
Это всё не важно. **FastAPI** знает, что нужно сделать. 😎
Это не важно. **FastAPI** знает, что делать.
/// note | Информация
/// note | Примечание
Если вам эта тема не знакома, прочтите [Async: *"In a hurry?"*](../../async.md){.internal-link target=_blank} раздел о `async` и `await` в документации.
Если вы не уверены, посмотрите раздел [Async: *"In a hurry?"*](../../async.md#in-a-hurry){.internal-link target=_blank} о `async` и `await` в документации.
///
## Интеграция с OpenAPI
## Интеграция с OpenAPI { #integrated-with-openapi }
Все заявления о запросах, валидаторы, требования ваших зависимостей (и подзависимостей) будут интегрированы в соответствующую OpenAPI-схему.
Все объявления запросов, проверки и требования ваших зависимостей (и подзависимостей) будут интегрированы в ту же схему OpenAPI.
В интерактивной документации будет вся информация по этим зависимостям тоже:
Поэтому в интерактивной документации будет вся информация и из этих зависимостей:
<img src="/img/tutorial/dependencies/image01.png">
## Простое использование
## Простое использование { #simple-usage }
Если вы посмотрите на фото, *функция обработки пути* объявляется каждый раз, когда вычисляется путь, и тогда **FastAPI** позаботится о вызове функции с корректными параметрами, извлекая информацию из запроса.
Если посмотреть, *функции обработки пути* объявляются для использования всякий раз, когда *путь* и *операция* совпадают, и тогда **FastAPI** заботится о вызове функции с корректными параметрами, извлекая данные из запроса.
На самом деле, все (или большинство) веб-фреймворков работают по схожему сценарию.
На самом деле все (или большинство) веб-фреймворков работают таким же образом.
Вы никогда не вызываете эти функции на месте. Их вызовет ваш фреймворк (в нашем случае, **FastAPI**).
Вы никогда не вызываете эти функции напрямую. Их вызывает ваш фреймворк (в нашем случае **FastAPI**).
С системой Dependency Injection, вы можете сообщить **FastAPI**, что ваша *функция обработки пути* "зависит" от чего-то ещё, что должно быть извлечено перед вашей *функцией обработки пути*, и **FastAPI** позаботится об извлечении и инъекции результата.
С системой **Dependency Injection** вы также можете сообщить **FastAPI**, что ваша *функция обработки пути* «зависит» от чего-то, что должно быть выполнено перед вашей *функцией обработки пути*, и **FastAPI** позаботится о его выполнении и «инъекции» результатов.
Другие распространённые термины для описания схожей идеи "dependency injection" являются:
Другие распространённые термины для описания той же идеи «dependency injection»:
- ресурсность
- доставка
- сервисность
- инъекция
- компонентность
* ресурсы
* провайдеры
* сервисы
* внедряемые зависимости
* компоненты
## **FastAPI** подключаемые модули
## Плагины **FastAPI** { #fastapi-plug-ins }
Инъекции и модули могут быть построены с использованием системы **Dependency Injection**. Но на самом деле, **нет необходимости создавать новые модули**, просто используя зависимости, можно объявить бесконечное количество интеграций и взаимодействий, которые доступны вашей *функции обработки пути*.
Интеграции и «плагины» могут быть построены с использованием системы **Dependency Injection**. Но на самом деле **нет необходимости создавать «плагины»**, так как, используя зависимости, можно объявить бесконечное количество интеграций и взаимодействий, которые становятся доступными вашим *функциям обработки пути*.
И зависимости могут быть созданы очень простым и интуитивным способом, что позволяет вам просто импортировать нужные пакеты Python и интегрировать их в API функции за пару строк.
И зависимости можно создавать очень простым и интуитивным способом, который позволяет просто импортировать нужные пакеты Python и интегрировать их с вашими API-функциями в пару строк кода, *буквально*.
Вы увидите примеры этого в следующих главах о реляционных и NoSQL базах данных, безопасности и т.д.
## Совместимость с **FastAPI**
## Совместимость с **FastAPI** { #fastapi-compatibility }
Простота Dependency Injection делает **FastAPI** совместимым с:
Простота системы **Dependency Injection** делает **FastAPI** совместимым с:
- всеми реляционными базами данных
- NoSQL базами данных
- внешними пакетами
- внешними API
- системами авторизации, аутентификации
- системами мониторинга использования API
- системами ввода данных ответов
- и так далее.
* всеми реляционными базами данных
* NoSQL базами данных
* внешними пакетами
* внешними API
* системами аутентификации и авторизации
* системами мониторинга использования API
* системами инъекции данных в ответы
* и т.
## Просто и сильно
## Просто и мощно { #simple-and-powerful }
Хотя иерархическая система Dependency Injection очень проста для описания и использования, она по-прежнему очень мощная.
Хотя иерархическая система dependency injection очень проста для определения и использования, она по-прежнему очень мощная.
Вы можете описывать зависимости в очередь, и они уже будут вызываться друг за другом.
Вы можете определять зависимости, которые, в свою очередь, могут иметь собственные зависимости.
Когда иерархическое дерево построено, система **Dependency Injection** берет на себя решение всех зависимостей для вас (и их подзависимостей) и обеспечивает (инъектирует) результат на каждом шаге.
В итоге строится иерархическое дерево зависимостей, и система **Dependency Injection** берёт на себя решение всех этих зависимостей (и их подзависимостей) и предоставляет (инъектирует) результаты на каждом шаге.
Например, у вас есть 4 API-эндпоинта (*операции пути*):
- `/items/public/`
- `/items/private/`
- `/users/{user_id}/activate`
- `/items/pro/`
* `/items/public/`
* `/items/private/`
* `/users/{user_id}/activate`
* `/items/pro/`
Тогда вы можете требовать разные права для каждого из них, используя зависимости и подзависимости:
тогда вы можете добавить разные требования к правам для каждого из них только с помощью зависимостей и подзависимостей:
```mermaid
graph TB
@@ -237,8 +243,8 @@ admin_user --> activate_user
paying_user --> pro_items
```
## Интегрировано с **OpenAPI**
## Интегрировано с **OpenAPI** { #integrated-with-openapi_1 }
Все эти зависимости, объявляя свои требования, также добавляют параметры, проверки и т.д. к вашим операциям *path*.
Все эти зависимости, объявляя свои требования, также добавляют параметры, проверки и т.д. к вашим *операциям пути*.
**FastAPI** позаботится о добавлении всего этого в схему открытого API, чтобы это отображалось в системах интерактивной документации.
**FastAPI** позаботится о добавлении всего этого в схему OpenAPI, чтобы это отображалось в системах интерактивной документации.

View File

@@ -1,4 +1,4 @@
# Подзависимости
# Подзависимости { #sub-dependencies }
Вы можете создавать зависимости, которые имеют **подзависимости**.
@@ -6,7 +6,7 @@
**FastAPI** сам займётся их управлением.
## Провайдер зависимости
## Первая зависимость { #first-dependency-dependable }
Можно создать первую зависимость следующим образом:
@@ -16,9 +16,9 @@
Это довольно просто (хотя и не очень полезно), но поможет нам сосредоточиться на том, как работают подзависимости.
## Вторая зависимость
## Вторая зависимость, «зависимость» и «зависимая» { #second-dependency-dependable-and-dependant }
Затем можно создать еще одну функцию зависимости, которая в то же время содержит внутри себя первую зависимость (таким образом, она тоже является "зависимой"):
Затем можно создать еще одну функцию зависимости, которая одновременно объявляет свою собственную зависимость (таким образом, она тоже является «зависимой»):
{* ../../docs_src/dependencies/tutorial005_an_py310.py hl[13] *}
@@ -29,7 +29,7 @@
* Она также объявляет необязательный куки-параметр `last_query` в виде строки.
* Если пользователь не указал параметр `q` в запросе, то мы используем последний использованный запрос, который мы ранее сохранили в куки-параметре `last_query`.
## Использование зависимости
## Использование зависимости { #use-the-dependency }
Затем мы можем использовать зависимость вместе с:
@@ -54,7 +54,7 @@ read_query["/items/"]
query_extractor --> query_or_cookie_extractor --> read_query
```
## Использование одной и той же зависимости несколько раз
## Использование одной и той же зависимости несколько раз { #using-the-same-dependency-multiple-times }
Если одна из ваших зависимостей объявлена несколько раз для одной и той же *функции операции пути*, например, несколько зависимостей имеют общую подзависимость, **FastAPI** будет знать, что вызывать эту подзависимость нужно только один раз за запрос.
@@ -62,7 +62,7 @@ query_extractor --> query_or_cookie_extractor --> read_query
В расширенном сценарии, когда вы знаете, что вам нужно, чтобы зависимость вызывалась на каждом шаге (возможно, несколько раз) в одном и том же запросе, вместо использования "кэшированного" значения, вы можете установить параметр `use_cache=False` при использовании `Depends`:
//// tab | Python 3.6+
//// tab | Python 3.8+
```Python hl_lines="1"
async def needy_dependency(fresh_value: Annotated[str, Depends(get_value, use_cache=False)]):
@@ -71,7 +71,7 @@ async def needy_dependency(fresh_value: Annotated[str, Depends(get_value, use_ca
////
//// tab | Python 3.6+ без Annotated
//// tab | Python 3.8+ без Annotated
/// tip | Подсказка
@@ -86,7 +86,7 @@ async def needy_dependency(fresh_value: str = Depends(get_value, use_cache=False
////
## Резюме
## Резюме { #recap }
Помимо всех этих умных слов, используемых здесь, система внедрения зависимостей довольно проста.

View File

@@ -1,4 +1,4 @@
# JSON кодировщик
# JSON-совместимый кодировщик { #json-compatible-encoder }
В некоторых случаях может потребоваться преобразование типа данных (например, Pydantic-модели) в тип, совместимый с JSON (например, `dict`, `list` и т.д.).
@@ -6,7 +6,7 @@
Для этого **FastAPI** предоставляет функцию `jsonable_encoder()`.
## Использование `jsonable_encoder`
## Использование `jsonable_encoder` { #using-the-jsonable-encoder }
Представим, что у вас есть база данных `fake_db`, которая принимает только JSON-совместимые данные.
@@ -14,21 +14,21 @@
В таком случае объект `datetime` следует преобразовать в строку соответствующую <a href="https://en.wikipedia.org/wiki/ISO_8601" class="external-link" target="_blank">формату ISO</a>.
Точно так же эта база данных не может принять Pydantic модель (объект с атрибутами), а только `dict`.
Точно так же эта база данных не может принять Pydantic-модель (объект с атрибутами), а только `dict`.
Для этого можно использовать функцию `jsonable_encoder`.
Она принимает объект, например, модель Pydantic, и возвращает его версию, совместимую с JSON:
Она принимает объект, например, Pydantic-модель, и возвращает его версию, совместимую с JSON:
{* ../../docs_src/encoder/tutorial001_py310.py hl[4,21] *}
В данном примере она преобразует Pydantic модель в `dict`, а `datetime` - в `str`.
В данном примере она преобразует Pydantic-модель в `dict`, а `datetime` - в `str`.
Результатом её вызова является объект, который может быть закодирован с помощью функции из стандартной библиотеки Python <a href="https://docs.python.org/3/library/json.html#json.dumps" class="external-link" target="_blank">`json.dumps()`</a>.
Функция не возвращает большой `str`, содержащий данные в формате JSON (в виде строки). Она возвращает стандартную структуру данных Python (например, `dict`) со значениями и подзначениями, которые совместимы с JSON.
/// note | Технические детали
/// note | Примечание
`jsonable_encoder` фактически используется **FastAPI** внутри системы для преобразования данных. Однако он полезен и во многих других сценариях.

View File

@@ -1,4 +1,4 @@
# Дополнительные типы данных
# Дополнительные типы данных { #extra-data-types }
До сих пор вы использовали простые типы данных, такие как:
@@ -9,15 +9,15 @@
Но вы также можете использовать и более сложные типы.
При этом у вас останутся те же возможности , что и до сих пор:
При этом у вас останутся те же возможности, что и до сих пор:
* Отличная поддержка редактора.
* Отличная поддержка редактора кода.
* Преобразование данных из входящих запросов.
* Преобразование данных для ответа.
* Валидация данных.
* Автоматическая аннотация и документация.
## Другие типы данных
## Другие типы данных { #other-data-types }
Ниже перечислены некоторые из дополнительных типов данных, которые вы можете использовать:
@@ -36,7 +36,7 @@
* `datetime.timedelta`:
* Встроенный в Python `datetime.timedelta`.
* В запросах и ответах будет представлен в виде общего количества секунд типа `float`.
* Pydantic также позволяет представить его как "Кодировку разницы во времени ISO 8601", <a href="https://docs.pydantic.dev/latest/concepts/serialization/#json_encoders" class="external-link" target="_blank">см. документацию для получения дополнительной информации</a>.
* Pydantic также позволяет представить его как "Кодировку разницы во времени ISO 8601", <a href="https://docs.pydantic.dev/latest/concepts/serialization/#custom-serializers" class="external-link" target="_blank">см. документацию для получения дополнительной информации</a>.
* `frozenset`:
* В запросах и ответах обрабатывается так же, как и `set`:
* В запросах будет прочитан список, исключены дубликаты и преобразован в `set`.
@@ -49,14 +49,14 @@
* `Decimal`:
* Встроенный в Python `Decimal`.
* В запросах и ответах обрабатывается так же, как и `float`.
* Вы можете проверить все допустимые типы данных pydantic здесь: <a href="https://docs.pydantic.dev/latest/concepts/types/" class="external-link" target="_blank">Типы данных Pydantic</a>.
* Вы можете проверить все допустимые типы данных Pydantic здесь: <a href="https://docs.pydantic.dev/latest/usage/types/types/" class="external-link" target="_blank">Типы данных Pydantic</a>.
## Пример
## Пример { #example }
Вот пример *операции пути* с параметрами, который демонстрирует некоторые из вышеперечисленных типов.
{* ../../docs_src/extra_data_types/tutorial001.py hl[1,3,12:16] *}
{* ../../docs_src/extra_data_types/tutorial001_an_py310.py hl[1,3,12:16] *}
Обратите внимание, что параметры внутри функции имеют свой естественный тип данных, и вы, например, можете выполнять обычные манипуляции с датами, такие как:
{* ../../docs_src/extra_data_types/tutorial001.py hl[18:19] *}
{* ../../docs_src/extra_data_types/tutorial001_an_py310.py hl[18:19] *}

View File

@@ -1,4 +1,4 @@
# Дополнительные модели
# Дополнительные модели { #extra-models }
В продолжение прошлого примера будет уже обычным делом иметь несколько связанных между собой моделей.
@@ -16,15 +16,23 @@
///
## Множественные модели
## Множественные модели { #multiple-models }
Ниже изложена основная идея того, как могут выглядеть эти модели с полями для паролей, а также описаны места, где они используются:
{* ../../docs_src/extra_models/tutorial001_py310.py hl[7,9,14,20,22,27:28,31:33,38:39] *}
### Про `**user_in.dict()`
/// info | Информация
#### `.dict()` из Pydantic
В Pydantic v1 метод назывался `.dict()`, в Pydantic v2 он помечен как устаревший (но всё ещё поддерживается) и переименован в `.model_dump()`.
В примерах здесь используется `.dict()` для совместимости с Pydantic v1, но если вы используете Pydantic v2, следует использовать `.model_dump()`.
///
### Про `**user_in.dict()` { #about-user-in-dict }
#### `.dict()` из Pydantic { #pydantics-dict }
`user_in` - это Pydantic-модель класса `UserIn`.
@@ -61,7 +69,7 @@ print(user_dict)
}
```
#### Распаковка `dict`
#### Распаковка `dict` { #unpacking-a-dict }
Если мы возьмём `dict` наподобие `user_dict` и передадим его в функцию (или класс), используя `**user_dict`, Python распакует его. Он передаст ключи и значения `user_dict` напрямую как аргументы типа ключ-значение.
@@ -93,7 +101,7 @@ UserInDB(
)
```
#### Pydantic-модель из содержимого другой модели
#### Pydantic-модель из содержимого другой модели { #a-pydantic-model-from-the-contents-of-another }
Как в примере выше мы получили `user_dict` из `user_in.dict()`, этот код:
@@ -112,7 +120,7 @@ UserInDB(**user_in.dict())
Таким образом мы получаем Pydantic-модель на основе данных из другой Pydantic-модели.
#### Распаковка `dict` и дополнительные именованные аргументы
#### Распаковка `dict` и дополнительные именованные аргументы { #unpacking-a-dict-and-extra-keywords }
И затем, если мы добавим дополнительный именованный аргумент `hashed_password=hashed_password` как здесь:
@@ -134,11 +142,11 @@ UserInDB(
/// warning | Предупреждение
Цель использованных в примере вспомогательных функций - не более чем демонстрация возможных операций с данными, но, конечно, они не обеспечивают настоящую безопасность.
Вспомогательные функции `fake_password_hasher` и `fake_save_user` используются только для демонстрации возможного потока данных и, конечно, не обеспечивают настоящую безопасность.
///
## Сократите дублирование
## Сократите дублирование { #reduce-duplication }
Сокращение дублирования кода - это одна из главных идей **FastAPI**.
@@ -156,13 +164,13 @@ UserInDB(
{* ../../docs_src/extra_models/tutorial002_py310.py hl[7,13:14,17:18,21:22] *}
## `Union` или `anyOf`
## `Union` или `anyOf` { #union-or-anyof }
Вы можете определить ответ как `Union` из двух типов. Это означает, что ответ должен соответствовать одному из них.
Вы можете определить ответ как `Union` из двух или более типов. Это означает, что ответ должен соответствовать одному из них.
Он будет определён в OpenAPI как `anyOf`.
Для этого используйте стандартные аннотации типов в Python <a href="https://docs.python.org/3/library/typing.html#typing.Union" class="external-link" target="_blank">`typing.Union`</a>:
Для этого используйте стандартную аннотацию типов в Python <a href="https://docs.python.org/3/library/typing.html#typing.Union" class="external-link" target="_blank">`typing.Union`</a>:
/// note | Примечание
@@ -172,7 +180,7 @@ UserInDB(
{* ../../docs_src/extra_models/tutorial003_py310.py hl[1,14:15,18:20,33] *}
### `Union` в Python 3.10
### `Union` в Python 3.10 { #union-in-python-3-10 }
В этом примере мы передаём `Union[PlaneItem, CarItem]` в качестве значения аргумента `response_model`.
@@ -186,7 +194,7 @@ some_variable: PlaneItem | CarItem
Но если мы помещаем его в `response_model=PlaneItem | CarItem` мы получим ошибку, потому что Python попытается произвести **некорректную операцию** между `PlaneItem` и `CarItem` вместо того, чтобы интерпретировать это как аннотацию типа.
## Список моделей
## Список моделей { #list-of-models }
Таким же образом вы можете определять ответы как списки объектов.
@@ -194,7 +202,7 @@ some_variable: PlaneItem | CarItem
{* ../../docs_src/extra_models/tutorial004_py39.py hl[18] *}
## Ответ с произвольным `dict`
## Ответ с произвольным `dict` { #response-with-arbitrary-dict }
Вы также можете определить ответ, используя произвольный одноуровневый `dict` и определяя только типы ключей и значений без использования Pydantic-моделей.
@@ -204,7 +212,7 @@ some_variable: PlaneItem | CarItem
{* ../../docs_src/extra_models/tutorial005_py39.py hl[6] *}
## Резюме
## Резюме { #recap }
Используйте несколько Pydantic-моделей и свободно применяйте наследование для каждой из них.

View File

@@ -1,106 +1,122 @@
# Первые шаги
# Первые шаги { #first-steps }
Самый простой FastAPI файл может выглядеть так:
Самый простой файл FastAPI может выглядеть так:
{* ../../docs_src/first_steps/tutorial001.py *}
Скопируйте в файл `main.py`.
Скопируйте это в файл `main.py`.
Запустите сервер в режиме реального времени:
<div class="termy">
```console
$ uvicorn main:app --reload
$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:solid">main.py</u>
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
<span style="color: green;">INFO</span>: Started reloader process [28720]
<span style="color: green;">INFO</span>: Started server process [28722]
<span style="color: green;">INFO</span>: Waiting for application startup.
<span style="color: green;">INFO</span>: Application startup complete.
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting development server 🚀
Searching for package file structure from directories
with <font color="#3465A4">__init__.py</font> files
Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
<span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> 🐍 main.py
<span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with
the following code:
<u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u>
<span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000/docs</u></font>
<span style="background-color:#007166"><font color="#D3D7CF"> tip </font></span> Running in development mode, for production use:
<b>fastapi run</b>
Logs:
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Will watch for changes in these directories:
<b>[</b><font color="#4E9A06">&apos;/home/user/code/awesomeapp&apos;</font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font> <b>(</b>Press CTRL+C
to quit<b>)</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started reloader process <b>[</b><font color="#34E2E2"><b>383138</b></font><b>]</b> using WatchFiles
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>383153</b></font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
```
</div>
/// note | Технические детали
Команда `uvicorn main:app` обращается к:
* `main`: файл `main.py` (модуль Python).
* `app`: объект, созданный внутри файла `main.py` в строке `app = FastAPI()`.
* `--reload`: перезапускает сервер после изменения кода. Используйте только для разработки.
///
В окне вывода появится следующая строка:
В выводе будет строка примерно такого вида:
```hl_lines="4"
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
Эта строка показывает URL-адрес, по которому приложение доступно на локальной машине.
Эта строка показывает URL, по которому ваше приложение доступно на локальной машине.
### Проверьте
### Проверьте { #check-it }
Откройте браузер по адресу: <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>.
Вы увидите JSON-ответ следующего вида:
Вы увидите JSON-ответ вида:
```JSON
{"message": "Hello World"}
```
### Интерактивная документация API
### Интерактивная документация API { #interactive-api-docs }
Перейдите по адресу: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
Теперь перейдите по адресу: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
Вы увидите автоматически сгенерированную, интерактивную документацию по API (предоставленную <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>):
Вы увидите автоматически сгенерированную интерактивную документацию по API (предоставлено <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>):
![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png)
### Альтернативная документация API
### Альтернативная документация API { #alternative-api-docs }
Теперь перейдите по адресу <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>.
И теперь перейдите по адресу <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>.
Вы увидите альтернативную автоматически сгенерированную документацию (предоставленную <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>):
Вы увидите альтернативную автоматически сгенерированную документацию (предоставлено <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>):
![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png)
### OpenAPI
### OpenAPI { #openapi }
**FastAPI** генерирует "схему" всего API, используя стандарт **OpenAPI**.
**FastAPI** генерирует «схему» всего вашего API, используя стандарт **OpenAPI** для описания API.
#### "Схема"
#### «Схема» { #schema }
"Схема" - это определение или описание чего-либо. Не код, реализующий это, а только абстрактное описание.
«Схема» — это определение или описание чего-либо. Не код, который это реализует, а только абстрактное описание.
#### API "схема"
#### «Схема» API { #api-schema }
<a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> - это спецификация, которая определяет, как описывать схему API.
В данном случае <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> это спецификация, которая определяет, как описывать схему вашего API.
Определение схемы содержит пути (paths) API, их параметры и т.п.
Это определение схемы включает пути вашего API, возможные параметры, которые они принимают, и т. п.
#### "Схема" данных
#### «Схема» данных { #data-schema }
Термин "схема" также может относиться к формату или структуре некоторых данных, например, JSON.
Термин «схема» также может относиться к форме некоторых данных, например, к содержимому JSON.
Тогда, подразумеваются атрибуты JSON, их типы данных и т.п.
В таком случае это будут атрибуты JSON, их типы данных и т. п.
#### OpenAPI и JSON Schema
#### OpenAPI и JSON Schema { #openapi-and-json-schema }
OpenAPI описывает схему API. Эта схема содержит определения (или "схемы") данных, отправляемых и получаемых API. Для описания структуры данных в JSON используется стандарт **JSON Schema**.
OpenAPI определяет схему API для вашего API. И эта схема включает определения (или «схемы») данных, отправляемых и получаемых вашим API, с использованием стандарта **JSON Schema** для схем данных JSON.
#### Рассмотрим `openapi.json`
#### Посмотрите `openapi.json` { #check-the-openapi-json }
Если Вас интересует, как выглядит исходная схема OpenAPI, то FastAPI автоматически генерирует JSON-схему со всеми описаниями API.
Если вам интересно, как выглядит исходная схема OpenAPI, FastAPI автоматически генерирует JSON (схему) с описанием всего вашего API.
Можете посмотреть здесь: <a href="http://127.0.0.1:8000/openapi.json" class="external-link" target="_blank">http://127.0.0.1:8000/openapi.json</a>.
Вы можете посмотреть её напрямую по адресу: <a href="http://127.0.0.1:8000/openapi.json" class="external-link" target="_blank">http://127.0.0.1:8000/openapi.json</a>.
Вы увидите примерно такой JSON:
Вы увидите JSON, начинающийся примерно так:
```JSON
{
"openapi": "3.0.2",
"openapi": "3.1.0",
"info": {
"title": "FastAPI",
"version": "0.1.0"
@@ -119,151 +135,123 @@ OpenAPI описывает схему API. Эта схема содержит о
...
```
#### Для чего нужен OpenAPI
#### Для чего нужен OpenAPI { #what-is-openapi-for }
Схема OpenAPI является основой для обеих систем интерактивной документации.
Схема OpenAPI является основой для обеих включённых систем интерактивной документации.
Существуют десятки альтернативных инструментов, основанных на OpenAPI. Вы можете легко добавить любой из них к **FastAPI** приложению.
Есть десятки альтернатив, все основаны на OpenAPI. Вы можете легко добавить любую из них в ваше приложение, созданное с **FastAPI**.
Вы также можете использовать OpenAPI для автоматической генерации кода для клиентов, которые взаимодействуют с API. Например, для фронтенд-, мобильных или IoT-приложений.
Вы также можете использовать её для автоматической генерации кода для клиентов, которые взаимодействуют с вашим API. Например, для фронтенд-, мобильных или IoT-приложений.
## Рассмотрим поэтапно
## Рассмотрим поэтапно { #recap-step-by-step }
### Шаг 1: импортируйте `FastAPI`
### Шаг 1: импортируйте `FastAPI` { #step-1-import-fastapi }
{* ../../docs_src/first_steps/tutorial001.py hl[1] *}
`FastAPI` это класс в Python, который предоставляет всю функциональность для API.
`FastAPI` это класс на Python, который предоставляет всю функциональность для вашего API.
/// note | Технические детали
`FastAPI` это класс, который наследуется непосредственно от `Starlette`.
`FastAPI` это класс, который напрямую наследуется от `Starlette`.
Вы можете использовать всю функциональность <a href="https://www.starlette.io/" class="external-link" target="_blank">Starlette</a> в `FastAPI`.
Вы можете использовать весь функционал <a href="https://www.starlette.io/" class="external-link" target="_blank">Starlette</a> и в `FastAPI`.
///
### Шаг 2: создайте экземпляр `FastAPI`
### Шаг 2: создайте экземпляр `FastAPI` { #step-2-create-a-fastapi-instance }
{* ../../docs_src/first_steps/tutorial001.py hl[3] *}
Переменная `app` является экземпляром класса `FastAPI`.
Здесь переменная `app` будет экземпляром класса `FastAPI`.
Это единая точка входа для создания и взаимодействия с API.
Это будет основная точка взаимодействия для создания всего вашего API.
Именно к этой переменной `app` обращается `uvicorn` в команде:
### Шаг 3: создайте *операцию пути (path operation)* { #step-3-create-a-path-operation }
<div class="termy">
#### Путь (path) { #path }
```console
$ uvicorn main:app --reload
Здесь «путь» — это последняя часть URL, начиная с первого символа `/`.
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
Если создать такое приложение:
{* ../../docs_src/first_steps/tutorial002.py hl[3] *}
И поместить его в `main.py`, тогда вызов `uvicorn` будет таким:
<div class="termy">
```console
$ uvicorn main:my_awesome_api --reload
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
### Шаг 3: определите *операцию пути (path operation)*
#### Путь (path)
"Путь" это часть URL, после первого символа `/`, следующего за именем домена.
Для URL:
Итак, в таком URL:
```
https://example.com/items/foo
```
...путь выглядит так:
...путь будет:
```
/items/foo
```
/// info | Дополнительная иформация
/// info | Информация
Термин "path" также часто называется "endpoint" или "route".
«Путь» также часто называют «эндпоинт» или «маршрут».
///
При создании API, "путь" является основным способом разделения "задач" и "ресурсов".
При создании API «путь» — это основной способ разделения «задач» и «ресурсов».
#### Операция (operation)
#### Операция (operation) { #operation }
"Операция" это один из "методов" HTTP.
«Операция» здесь — это один из HTTP-«методов».
Таких, как:
Один из:
* `POST`
* `GET`
* `PUT`
* `DELETE`
...и более экзотических:
...и более экзотические:
* `OPTIONS`
* `HEAD`
* `PATCH`
* `TRACE`
По протоколу HTTP можно обращаться к каждому пути, используя один (или несколько) из этих "методов".
В протоколе HTTP можно обращаться к каждому пути, используя один (или несколько) из этих «методов».
---
При создании API принято использовать конкретные HTTP-методы для выполнения определенных действий.
При создании API обычно используют конкретные HTTP-методы для выполнения конкретных действий.
Обычно используют:
* `POST`: создать данные.
* `GET`: прочитать.
* `PUT`: изменить (обновить).
* `DELETE`: удалить.
* `GET`: прочитать данные.
* `PUT`: обновить данные.
* `DELETE`: удалить данные.
В OpenAPI каждый HTTP метод называется "**операция**".
Таким образом, в OpenAPI каждый HTTP-метод называется «операцией».
Мы также будем придерживаться этого термина.
Мы тоже будем называть их «операциями».
#### Определите *декоратор операции пути (path operation decorator)*
#### Определите *декоратор операции пути (path operation decorator)* { #define-a-path-operation-decorator }
{* ../../docs_src/first_steps/tutorial001.py hl[6] *}
Декоратор `@app.get("/")` указывает **FastAPI**, что функция, прямо под ним, отвечает за обработку запросов, поступающих по адресу:
`@app.get("/")` сообщает **FastAPI**, что функция прямо под ним отвечает за обработку запросов, поступающих:
* путь `/`
* использующих <abbr title="HTTP GET метод"><code>get</code> операцию</abbr>
* по пути `/`
* с использованием <abbr title="метод HTTP GET"><code>get</code> операции</abbr>
/// info | `@decorator` Дополнительная информация
/// info | Информация о `@decorator`
Синтаксис `@something` в Python называется "декоратор".
Синтаксис `@something` в Python называется «декоратор».
Вы помещаете его над функцией. Как красивую декоративную шляпу (думаю, что оттуда и происходит этот термин).
Его размещают над функцией. Как красивая декоративная шляпа (кажется, отсюда и пошёл термин).
"Декоратор" принимает функцию ниже и выполняет с ней какое-то действие.
«Декоратор» берёт функцию ниже и делает с ней что-то.
В нашем случае, этот декоратор сообщает **FastAPI**, что функция ниже соответствует **пути** `/` и **операции** `get`.
В нашем случае этот декоратор сообщает **FastAPI**, что функция ниже соответствует **пути** `/` с **операцией** `get`.
Это и есть "**декоратор операции пути**".
Это и есть «декоратор операции пути».
///
Можно также использовать операции:
Можно также использовать другие операции:
* `@app.post()`
* `@app.put()`
@@ -278,58 +266,58 @@ https://example.com/items/foo
/// tip | Подсказка
Вы можете использовать каждую операцию (HTTP-метод) по своему усмотрению.
Вы можете использовать каждый метод (HTTP-операцию) так, как считаете нужным.
**FastAPI** не навязывает определенного значения для каждого метода.
**FastAPI** не навязывает какого-либо конкретного смысла.
Информация здесь представлена как рекомендация, а не требование.
Эта информация дана как рекомендация, а не требование.
Например, при использовании GraphQL обычно все действия выполняются только с помощью POST операций.
Например, при использовании GraphQL обычно все действия выполняются только с помощью POST-операций.
///
### Шаг 4: определите **функцию операции пути**
### Шаг 4: определите **функцию операции пути** { #step-4-define-the-path-operation-function }
Вот "**функция операции пути**":
Вот наша «функция операции пути»:
* **путь**: `/`.
* **операция**: `get`.
* **функция**: функция ниже "декоратора" (ниже `@app.get("/")`).
* **функция**: функция ниже «декоратора» (ниже `@app.get("/")`).
{* ../../docs_src/first_steps/tutorial001.py hl[7] *}
Это обычная Python функция.
Это функция на Python.
**FastAPI** будет вызывать её каждый раз при получении `GET` запроса к URL "`/`".
**FastAPI** будет вызывать её каждый раз, когда получает запрос к URL «`/`» с операцией `GET`.
В данном случае это асинхронная функция.
В данном случае это асинхронная (`async`) функция.
---
Вы также можете определить ее как обычную функцию вместо `async def`:
Вы также можете определить её как обычную функцию вместо `async def`:
{* ../../docs_src/first_steps/tutorial003.py hl[7] *}
/// note | Технические детали
/// note | Примечание
Если не знаете в чём разница, посмотрите [Конкурентность: *"Нет времени?"*](../async.md#_1){.internal-link target=_blank}.
Если вы не знаете, в чём разница, посмотрите [Асинхронность: *"Нет времени?"*](../async.md#in-a-hurry){.internal-link target=_blank}.
///
### Шаг 5: верните результат
### Шаг 5: верните содержимое { #step-5-return-the-content }
{* ../../docs_src/first_steps/tutorial001.py hl[8] *}
Вы можете вернуть `dict`, `list`, отдельные значения `str`, `int` и т.д.
Также можно вернуть модели Pydantic (рассмотрим это позже).
Также можно вернуть модели Pydantic (подробнее об этом позже).
Многие объекты и модели будут автоматически преобразованы в JSON (включая ORM). Пробуйте использовать другие объекты, которые предпочтительней для Вас, вероятно, они уже поддерживаются.
Многие другие объекты и модели будут автоматически преобразованы в JSON (включая ORM и т. п.). Попробуйте использовать те, что вам привычнее, с высокой вероятностью они уже поддерживаются.
## Резюме
## Резюме { #recap }
* Импортируем `FastAPI`.
* Создаём экземпляр `app`.
* Пишем **декоратор операции пути** (такой как `@app.get("/")`).
* Пишем **функцию операции пути** (`def root(): ...`).
* Запускаем сервер в режиме разработки (`uvicorn main:app --reload`).
* Импортируйте `FastAPI`.
* Создайте экземпляр `app`.
* Напишите **декоратор операции пути**, например `@app.get("/")`.
* Определите **функцию операции пути**; например, `def root(): ...`.
* Запустите сервер разработки командой `fastapi dev`.

View File

@@ -1,4 +1,4 @@
# Обработка ошибок
# Обработка ошибок { #handling-errors }
Существует множество ситуаций, когда необходимо сообщить об ошибке клиенту, использующему ваш API.
@@ -19,15 +19,15 @@
Помните ли ошибки **"404 Not Found "** (и шутки) ?
## Использование `HTTPException`
## Использование `HTTPException` { #use-httpexception }
Для возврата клиенту HTTP-ответов с ошибками используется `HTTPException`.
### Импортируйте `HTTPException`
### Импортируйте `HTTPException` { #import-httpexception }
{* ../../docs_src/handling_errors/tutorial001.py hl[1] *}
### Вызовите `HTTPException` в своем коде
### Вызовите `HTTPException` в своем коде { #raise-an-httpexception-in-your-code }
`HTTPException` - это обычное исключение Python с дополнительными данными, актуальными для API.
@@ -41,7 +41,7 @@
{* ../../docs_src/handling_errors/tutorial001.py hl[11] *}
### Возвращаемый ответ
### Возвращаемый ответ { #the-resulting-response }
Если клиент запросит `http://example.com/items/foo` (`item_id` `"foo"`), то он получит статус-код 200 и ответ в формате JSON:
@@ -69,7 +69,7 @@
///
## Добавление пользовательских заголовков
## Добавление пользовательских заголовков { #add-custom-headers }
В некоторых ситуациях полезно иметь возможность добавлять пользовательские заголовки к ошибке HTTP. Например, для некоторых типов безопасности.
@@ -79,7 +79,7 @@
{* ../../docs_src/handling_errors/tutorial002.py hl[14] *}
## Установка пользовательских обработчиков исключений
## Установка пользовательских обработчиков исключений { #install-custom-exception-handlers }
Вы можете добавить пользовательские обработчики исключений с помощью <a href="https://www.starlette.io/exceptions/" class="external-link" target="_blank">то же самое исключение - утилиты от Starlette</a>.
@@ -109,7 +109,7 @@
///
## Переопределение стандартных обработчиков исключений
## Переопределение стандартных обработчиков исключений { #override-the-default-exception-handlers }
**FastAPI** имеет некоторые обработчики исключений по умолчанию.
@@ -117,7 +117,7 @@
Вы можете переопределить эти обработчики исключений на свои собственные.
### Переопределение исключений проверки запроса
### Переопределение исключений проверки запроса { #override-request-validation-exceptions }
Когда запрос содержит недопустимые данные, **FastAPI** внутренне вызывает ошибку `RequestValidationError`.
@@ -154,7 +154,7 @@ path -> item_id
value is not a valid integer (type=type_error.integer)
```
#### `RequestValidationError` или `ValidationError`
#### `RequestValidationError` или `ValidationError` { #requestvalidationerror-vs-validationerror }
/// warning | Внимание
@@ -172,7 +172,7 @@ path -> item_id
И пока вы не устраните ошибку, ваши клиенты/пользователи не должны иметь доступа к внутренней информации о ней, так как это может привести к уязвимости в системе безопасности.
### Переопределите обработчик ошибок `HTTPException`
### Переопределите обработчик ошибок `HTTPException` { #override-the-httpexception-error-handler }
Аналогичным образом можно переопределить обработчик `HTTPException`.
@@ -188,7 +188,7 @@ path -> item_id
///
### Используйте тело `RequestValidationError`
### Используйте тело `RequestValidationError` { #use-the-requestvalidationerror-body }
Ошибка `RequestValidationError` содержит полученное `тело` с недопустимыми данными.
@@ -226,21 +226,19 @@ path -> item_id
}
```
#### `HTTPException` в FastAPI или в Starlette
#### `HTTPException` в FastAPI или в Starlette { #fastapis-httpexception-vs-starlettes-httpexception }
**FastAPI** имеет собственный `HTTPException`.
Класс ошибок **FastAPI** `HTTPException` наследует от класса ошибок Starlette `HTTPException`.
Единственное отличие заключается в том, что `HTTPException` от **FastAPI** позволяет добавлять заголовки, которые будут включены в ответ.
Он необходим/используется внутри системы для OAuth 2.0 и некоторых утилит безопасности.
Единственное отличие состоит в том, что `HTTPException` в **FastAPI** принимает любые данные, пригодные для преобразования в JSON, в поле `detail`, тогда как `HTTPException` в Starlette принимает для него только строки.
Таким образом, вы можете продолжать вызывать `HTTPException` от **FastAPI** как обычно в своем коде.
Но когда вы регистрируете обработчик исключений, вы должны зарегистрировать его для `HTTPException` от Starlette.
Таким образом, если какая-либо часть внутреннего кода Starlette, расширение или плагин Starlette вызовет исключение Starlette `HTTPException`, ваш обработчик сможет перехватить и обработать его.
Таким образом, если какая-либо часть внутреннего кодa Starlette, расширение или плагин Starlette вызовет исключение Starlette `HTTPException`, ваш обработчик сможет перехватить и обработать его.
В данном примере, чтобы иметь возможность использовать оба `HTTPException` в одном коде, исключения Starlette переименованы в `StarletteHTTPException`:
@@ -248,7 +246,7 @@ path -> item_id
from starlette.exceptions import HTTPException as StarletteHTTPException
```
### Переиспользование обработчиков исключений **FastAPI**
### Переиспользование обработчиков исключений **FastAPI** { #reuse-fastapis-exception-handlers }
Если вы хотите использовать исключение вместе с теми же обработчиками исключений по умолчанию из **FastAPI**, вы можете импортировать и повторно использовать обработчики исключений по умолчанию из `fastapi.exception_handlers`:

View File

@@ -1,4 +1,4 @@
# Модели Header-параметров
# Модели Header-параметров { #header-parameter-models }
Если у вас есть группа связанных **header-параметров**, то вы можете объединить их в одну **Pydantic-модель**.
@@ -10,7 +10,7 @@
///
## Header-параметры в виде Pydantic-модели
## Header-параметры в виде Pydantic-модели { #header-parameters-with-a-pydantic-model }
Объявите нужные **header-параметры** в **Pydantic-модели** и затем аннотируйте параметр как `Header`:
@@ -18,7 +18,7 @@
**FastAPI** **извлечёт** данные для **каждого поля** из **заголовков** запроса и выдаст заданную вами Pydantic-модель.
## Проверьте документацию
## Проверьте документацию { #check-the-docs }
Вы можете посмотреть нужные header-параметры в графическом интерфейсе сгенерированной документации по пути `/docs`:
@@ -26,7 +26,7 @@
<img src="/img/tutorial/header-param-models/image01.png">
</div>
## Как запретить дополнительные заголовки
## Как запретить дополнительные заголовки { #forbid-extra-headers }
В некоторых случаях (не особо часто встречающихся) вам может понадобиться **ограничить** заголовки, которые вы хотите получать.
@@ -51,7 +51,7 @@
}
```
## Как отключить автоматическое преобразование подчеркиваний
## Как отключить автоматическое преобразование подчеркиваний { #disable-convert-underscores }
Как и в случае с обычными заголовками, если у вас в именах параметров имеются символы подчеркивания, они **автоматически преобразовываются в дефис**.
@@ -67,6 +67,6 @@
///
## Резюме
## Резюме { #summary }
Вы можете использовать **Pydantic-модели** для объявления **header-параметров** в **FastAPI**. 😎

View File

@@ -1,14 +1,14 @@
# Header-параметры
# Header-параметры { #header-parameters }
Вы можете определить параметры заголовка таким же образом, как вы определяете параметры `Query`, `Path` и `Cookie`.
## Импорт `Header`
## Импорт `Header` { #import-header }
Сперва импортируйте `Header`:
{* ../../docs_src/header_params/tutorial001_an_py310.py hl[3] *}
## Объявление параметров `Header`
## Объявление параметров `Header` { #declare-header-parameters }
Затем объявите параметры заголовка, используя ту же структуру, что и с `Path`, `Query` и `Cookie`.
@@ -24,13 +24,13 @@
///
/// info | Дополнительная информация
/// info | Информация
Чтобы объявить заголовки, важно использовать `Header`, иначе параметры интерпретируются как query-параметры.
///
## Автоматическое преобразование
## Автоматическое преобразование { #automatic-conversion }
`Header` обладает небольшой дополнительной функциональностью в дополнение к тому, что предоставляют `Path`, `Query` и `Cookie`.
@@ -54,7 +54,7 @@
///
## Повторяющиеся заголовки
## Повторяющиеся заголовки { #duplicate-headers }
Есть возможность получать несколько заголовков с одним и тем же именем, но разными значениями.
@@ -84,7 +84,7 @@ X-Token: bar
}
```
## Резюме
## Резюме { #recap }
Объявляйте заголовки с помощью `Header`, используя тот же общий шаблон, как при `Query`, `Path` и `Cookie`.

View File

@@ -1,83 +1,95 @@
# Учебник - Руководство пользователя
# Учебник - Руководство пользователя { #tutorial-user-guide }
В этом руководстве шаг за шагом показано, как использовать **FastApi** с большинством его функций.
В этом руководстве шаг за шагом показано, как использовать **FastAPI** с большинством его функций.
Каждый раздел постепенно основывается на предыдущих, но он структурирован по отдельным темам, так что вы можете перейти непосредственно к конкретной теме для решения ваших конкретных потребностей в API.
Каждый раздел постепенно основывается на предыдущих, но структура разделяет темы, так что вы можете сразу перейти к нужной теме для решения ваших конкретных задач по API.
Он также создан для использования в качестве будущего справочника.
Он также создан как справочник на будущее, чтобы вы могли вернуться и посмотреть именно то, что вам нужно.
Так что вы можете вернуться и посмотреть именно то, что вам нужно.
## Запустите код { #run-the-code }
## Запустите код
Все блоки кода можно копировать и использовать напрямую (это действительно протестированные файлы Python).
Все блоки кода можно копировать и использовать напрямую (на самом деле это проверенные файлы Python).
Чтобы запустить любой из примеров, скопируйте код в файл `main.py` и запустите `uvicorn` с параметрами:
Чтобы запустить любой из примеров, скопируйте код в файл `main.py` и запустите `fastapi dev` с:
<div class="termy">
```console
$ uvicorn main:app --reload
$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:solid">main.py</u>
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
<span style="color: green;">INFO</span>: Started reloader process [28720]
<span style="color: green;">INFO</span>: Started server process [28722]
<span style="color: green;">INFO</span>: Waiting for application startup.
<span style="color: green;">INFO</span>: Application startup complete.
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting development server 🚀
Searching for package file structure from directories
with <font color="#3465A4">__init__.py</font> files
Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
<span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> 🐍 main.py
<span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with
the following code:
<u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u>
<span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000/docs</u></font>
<span style="background-color:#007166"><font color="#D3D7CF"> tip </font></span> Running in development mode, for production use:
<b>fastapi run</b>
Logs:
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Will watch for changes in these directories:
<b>[</b><font color="#4E9A06">&apos;/home/user/code/awesomeapp&apos;</font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font> <b>(</b>Press CTRL+C
to quit<b>)</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started reloader process <b>[</b><font color="#34E2E2"><b>383138</b></font><b>]</b> using WatchFiles
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>383153</b></font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
```
</div>
**НАСТОЯТЕЛЬНО рекомендуется**, чтобы вы написали или скопировали код, отредактировали его и запустили локально.
**НАСТОЯТЕЛЬНО рекомендуется** написать или скопировать код, отредактировать его и запустить локально.
Использование кода в вашем редакторе — это то, что действительно показывает вам преимущества FastAPI, видя, как мало кода вам нужно написать, все проверки типов, автодополнение и т.д.
Использование кода в вашем редакторе кода — это то, что действительно показывает преимущества FastAPI: вы увидите, как мало кода нужно написать, все проверки типов, автозавершение и т.д.
---
## Установка FastAPI
## Установка FastAPI { #install-fastapi }
Первый шаг — установить FastAPI.
Для руководства вы, возможно, захотите установить его со всеми дополнительными зависимостями и функциями:
Убедитесь, что вы создали [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активировали его, и затем **установите FastAPI**:
<div class="termy">
```console
$ pip install "fastapi[all]"
$ pip install "fastapi[standard]"
---> 100%
```
</div>
...это также включает `uvicorn`, который вы можете использовать в качестве сервера, который запускает ваш код.
/// note | Примечание
/// note | Технические детали
При установке с помощью `pip install "fastapi[standard]"` добавляются некоторые стандартные необязательные зависимости по умолчанию, включая `fastapi-cloud-cli`, который позволяет развернуть приложение на <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>.
Вы также можете установить его по частям.
Если вы не хотите иметь эти необязательные зависимости, установите просто `pip install fastapi`.
Это то, что вы, вероятно, сделаете, когда захотите развернуть свое приложение в рабочей среде:
```
pip install fastapi
```
Также установите `uvicorn` для работы в качестве сервера:
```
pip install "uvicorn[standard]"
```
И то же самое для каждой из необязательных зависимостей, которые вы хотите использовать.
Если вы хотите установить стандартные зависимости, но без `fastapi-cloud-cli`, установите `pip install "fastapi[standard-no-fastapi-cloud-cli]"`.
///
## Продвинутое руководство пользователя
## Продвинутое руководство пользователя { #advanced-user-guide }
Существует также **Продвинутое руководство пользователя**, которое вы сможете прочитать после руководства **Учебник - Руководство пользователя**.
Существует также **Продвинутое руководство пользователя**, которое вы сможете прочитать после **Учебник - Руководство пользователя**.
**Продвинутое руководство пользователя** основано на этом, использует те же концепции и учит вас некоторым дополнительным функциям.
**Продвинутое руководство пользователя** основано на этом, использует те же концепции и обучает некоторым дополнительным функциям.
Но вы должны сначала прочитать **Учебник - Руководство пользователя** (то, что вы читаете прямо сейчас).
Но сначала вам следует прочитать **Учебник - Руководство пользователя** (то, что вы читаете прямо сейчас).
Он разработан таким образом, что вы можете создать полноценное приложение, используя только **Учебник - Руководство пользователя**, а затем расширить его различными способами, в зависимости от ваших потребностей, используя некоторые дополнительные идеи из **Продвинутого руководства пользователя**.
Оно спроектировано так, что вы можете создать полноценное приложение, используя только **Учебник - Руководство пользователя**, а затем расширить его различными способами, в зависимости от ваших потребностей, используя дополнительные идеи из **Продвинутого руководства пользователя**.

View File

@@ -1,23 +1,24 @@
# URL-адреса метаданных и документации
# URL-адреса метаданных и документации { #metadata-and-docs-urls }
Вы можете настроить несколько конфигураций метаданных в вашем **FastAPI** приложении.
## Метаданные для API
## Метаданные для API { #metadata-for-api }
Вы можете задать следующие поля, которые используются в спецификации OpenAPI и в UI автоматической документации API:
| Параметр | Тип | Описание |
|------------|--|-------------|
|------------|------|-------------|
| `title` | `str` | Заголовок API. |
| `summary` | `str` | Краткое резюме API. <small>Доступно начиная с OpenAPI 3.1.0, FastAPI 0.99.0.</small> |
| `description` | `str` | Краткое описание API. Может быть использован Markdown. |
| `version` | `string` | Версия API. Версия вашего собственного приложения, а не OpenAPI. К примеру `2.5.0`. |
| `terms_of_service` | `str` | Ссылка к условиям пользования API. Если указано, то это должен быть URL-адрес. |
| `contact` | `dict` | Контактная информация для открытого API. Может содержать несколько полей. <details><summary>поля <code>contact</code></summary><table><thead><tr><th>Параметр</th><th>Тип</th><th>Описание</th></tr></thead><tbody><tr><td><code>name</code></td><td><code>str</code></td><td>Идентификационное имя контактного лица/организации.</td></tr><tr><td><code>url</code></td><td><code>str</code></td><td>URL указывающий на контактную информацию. ДОЛЖЕН быть в формате URL.</td></tr><tr><td><code>email</code></td><td><code>str</code></td><td>Email адрес контактного лица/организации. ДОЛЖЕН быть в формате email адреса.</td></tr></tbody></table></details> |
| `license_info` | `dict` | Информация о лицензии открытого API. Может содержать несколько полей. <details><summary>поля <code>license_info</code></summary><table><thead><tr><th>Параметр</th><th>Тип</th><th>Описание</th></tr></thead><tbody><tr><td><code>name</code></td><td><code>str</code></td><td><strong>ОБЯЗАТЕЛЬНО</strong> (если установлен параметр <code>license_info</code>). Название лицензии, используемой для API</td></tr><tr><td><code>url</code></td><td><code>str</code></td><td>URL, указывающий на лицензию, используемую для API. ДОЛЖЕН быть в формате URL.</td></tr></tbody></table></details> |
| `license_info` | `dict` | Информация о лицензии открытого API. Может содержать несколько полей. <details><summary>поля <code>license_info</code></summary><table><thead><tr><th>Параметр</th><th>Тип</th><th>Описание</th></tr></thead><tbody><tr><td><code>name</code></td><td><code>str</code></td><td><strong>ОБЯЗАТЕЛЬНО</strong> (если установлен параметр <code>license_info</code>). Название лицензии, используемой для API.</td></tr><tr><td><code>identifier</code></td><td><code>str</code></td><td>Выражение лицензии <a href="https://spdx.org/licenses/" class="external-link" target="_blank">SPDX</a> для API. Поле <code>identifier</code> взаимоисключающее с полем <code>url</code>. <small>Доступно начиная с OpenAPI 3.1.0, FastAPI 0.99.0.</small></td></tr><tr><td><code>url</code></td><td><code>str</code></td><td>URL, указывающий на лицензию, используемую для API. ДОЛЖЕН быть в формате URL.</td></tr></tbody></table></details> |
Вы можете задать их следующим образом:
{* ../../docs_src/metadata/tutorial001.py hl[3:16,19:31] *}
{* ../../docs_src/metadata/tutorial001.py hl[3:16, 19:32] *}
/// tip | Подсказка
@@ -25,11 +26,19 @@
///
С этой конфигурацией автоматическая документация API будут выглядеть так:
С этой конфигурацией автоматическая документация API будет выглядеть так:
<img src="/img/tutorial/metadata/image01.png">
## Метаданные для тегов
## Идентификатор лицензии { #license-identifier }
Начиная с OpenAPI 3.1.0 и FastAPI 0.99.0, вы также можете задать `license_info` с помощью `identifier` вместо `url`.
К примеру:
{* ../../docs_src/metadata/tutorial001_1.py hl[31] *}
## Метаданные для тегов { #metadata-for-tags }
Вы также можете добавить дополнительные метаданные для различных тегов, используемых для группировки ваших операций пути с помощью параметра `openapi_tags`.
@@ -43,7 +52,7 @@
* `description`: `str`-значение с кратким описанием для внешней документации.
* `url` (**обязательно**): `str`-значение с URL-адресом для внешней документации.
### Создание метаданных для тегов
### Создание метаданных для тегов { #create-metadata-for-tags }
Давайте попробуем сделать это на примере с тегами для `users` и `items`.
@@ -59,30 +68,31 @@
///
### Используйте собственные теги
### Используйте собственные теги { #use-your-tags }
Используйте параметр `tags` с вашими *операциями пути*`APIRouter`ами), чтобы присвоить им различные теги:
{* ../../docs_src/metadata/tutorial004.py hl[21,26] *}
/// info | Дополнительная информация
Узнайте больше о тегах в [Конфигурации операции пути](path-operation-configuration.md#_3){.internal-link target=_blank}.
Узнайте больше о тегах в [Конфигурации операции пути](path-operation-configuration.md#tags){.internal-link target=_blank}.
///
### Проверьте документацию
### Проверьте документацию { #check-the-docs }
Теперь, если вы проверите документацию, вы увидите всю дополнительную информацию:
<img src="/img/tutorial/metadata/image02.png">
### Порядок расположения тегов
### Порядок расположения тегов { #order-of-tags }
Порядок расположения словарей метаданных для каждого тега определяет также порядок, отображаемый в документах UI
Порядок расположения словарей метаданных для каждого тега определяет также порядок, отображаемый в UI документации.
К примеру, несмотря на то, что `users` будут идти после `items` в алфавитном порядке, они отображаются раньше, потому что мы добавляем свои метаданные в качестве первого словаря в списке.
## URL-адреса OpenAPI
## URL-адрес OpenAPI { #openapi-url }
По умолчанию схема OpenAPI отображена по адресу `/openapi.json`.
@@ -92,11 +102,11 @@
{* ../../docs_src/metadata/tutorial002.py hl[3] *}
Если вы хотите отключить схему OpenAPI полностью, вы можете задать `openapi_url=None`, это также отключит пользовательские интерфейсы документации, которые его использует.
Если вы хотите отключить схему OpenAPI полностью, вы можете задать `openapi_url=None`, это также отключит пользовательские интерфейсы документации, которые её используют.
## URL-адреса документации
## URL-адреса документации { #docs-urls }
Вы можете изменить конфигурацию двух пользовательских интерфейсов документации, среди которых
Вы можете изменить конфигурацию двух пользовательских интерфейсов документации, которые включены:
* **Swagger UI**: отображаемый по адресу `/docs`.
* Вы можете задать его URL с помощью параметра `docs_url`.

View File

@@ -1,4 +1,4 @@
# Middleware (Промежуточный слой)
# Middleware (Промежуточный слой) { #middleware }
Вы можете добавить промежуточный слой (middleware) в **FastAPI** приложение.
@@ -17,11 +17,11 @@
Если у вас есть зависимости с `yield`, то код выхода (код после `yield`) будет выполняться *после* middleware.
Если у вас имеются некие фоновые задачи (см. документацию), то они будут запущены после middleware.
Если были какие‑либо фоновые задачи (рассматриваются в разделе [Фоновые задачи](background-tasks.md){.internal-link target=_blank}, вы увидите это позже), они будут запущены *после* всех middleware.
///
## Создание middleware
## Создание middleware { #create-a-middleware }
Для создания middleware используйте декоратор `@app.middleware("http")`.
@@ -51,7 +51,7 @@
///
### До и после `response`
### До и после `response` { #before-and-after-the-response }
Вы можете добавить код, использующий `request` до передачи его какой-либо *операции пути*.
@@ -67,8 +67,31 @@
///
## Другие middleware
## Порядок выполнения нескольких middleware { #multiple-middleware-execution-order }
Когда вы добавляете несколько middleware с помощью декоратора `@app.middleware()` или метода `app.add_middleware()`, каждое новое middleware оборачивает приложение, формируя стек. Последнее добавленное middleware — самое внешнее (*outermost*), а первое — самое внутреннее (*innermost*).
На пути обработки запроса сначала выполняется самое внешнее middleware.
На пути формирования ответа оно выполняется последним.
Например:
```Python
app.add_middleware(MiddlewareA)
app.add_middleware(MiddlewareB)
```
Это приводит к следующему порядку выполнения:
* **Запрос**: MiddlewareB → MiddlewareA → маршрут
* **Ответ**: маршрут → MiddlewareA → MiddlewareB
Такое стековое поведение обеспечивает предсказуемый и управляемый порядок выполнения middleware.
## Другие middleware { #other-middlewares }
О других middleware вы можете узнать больше в разделе [Advanced User Guide: Advanced Middleware](../advanced/middleware.md){.internal-link target=_blank}.
В следующем разделе вы можете прочитать, как настроить <abbr title="Cross-Origin Resource Sharing">CORS</abbr> с помощью middleware.
В следующем разделе вы можете прочитать, как настроить <abbr title="Cross-Origin Resource Sharing совместное использование ресурсов между источниками">CORS</abbr> с помощью middleware.

View File

@@ -1,4 +1,4 @@
# Конфигурация операций пути
# Конфигурация операций пути { #path-operation-configuration }
Существует несколько параметров, которые вы можете передать вашему *декоратору операций пути* для его настройки.
@@ -8,7 +8,7 @@
///
## Коды состояния
## Статус-код ответа { #response-status-code }
Вы можете определить (HTTP) `status_code`, который будет использован в ответах вашей *операции пути*.
@@ -18,7 +18,7 @@
{* ../../docs_src/path_operation_configuration/tutorial001_py310.py hl[1,15] *}
Этот код состояния будет использован в ответе и будет добавлен в схему OpenAPI.
Этот статус-код будет использован в ответе и будет добавлен в схему OpenAPI.
/// note | Технические детали
@@ -28,7 +28,7 @@
///
## Теги
## Теги { #tags }
Вы можете добавлять теги к вашим *операциям пути*, добавив параметр `tags` с `list` заполненным `str`-значениями (обычно в нём только одна строка):
@@ -38,7 +38,7 @@
<img src="/img/tutorial/path-operation-configuration/image01.png">
### Теги с перечислениями
### Теги с перечислениями { #tags-with-enums }
Если у вас большое приложение, вы можете прийти к необходимости добавить **несколько тегов**, и возможно, вы захотите убедиться в том, что всегда используете **один и тот же тег** для связанных *операций пути*.
@@ -48,13 +48,13 @@
{* ../../docs_src/path_operation_configuration/tutorial002b.py hl[1,8:10,13,18] *}
## Краткое и развёрнутое содержание
## Краткое и развёрнутое содержание { #summary-and-description }
Вы можете добавить параметры `summary` и `description`:
{* ../../docs_src/path_operation_configuration/tutorial003_py310.py hl[18:19] *}
## Описание из строк документации
## Описание из строк документации { #description-from-docstring }
Так как описания обычно длинные и содержат много строк, вы можете объявить описание *операции пути* в функции <abbr title="многострочный текст, первое выражение внутри функции (не присвоенный какой-либо переменной), используемый для документации">строки документации</abbr> и **FastAPI** прочитает её отсюда.
@@ -66,7 +66,7 @@
<img src="/img/tutorial/path-operation-configuration/image02.png">
## Описание ответа
## Описание ответа { #response-description }
Вы можете указать описание ответа с помощью параметра `response_description`:
@@ -78,7 +78,7 @@
///
/// check | Технические детали
/// check
OpenAPI указывает, что каждой *операции пути* необходимо описание ответа.
@@ -88,7 +88,7 @@ OpenAPI указывает, что каждой *операции пути* не
<img src="/img/tutorial/path-operation-configuration/image03.png">
## Обозначение *операции пути* как устаревшей
## Обозначение *операции пути* как устаревшей { #deprecate-a-path-operation }
Если вам необходимо пометить *операцию пути* как <abbr title="устаревшее, не рекомендовано к использованию">устаревшую</abbr>, при этом не удаляя её, передайте параметр `deprecated`:
@@ -102,6 +102,6 @@ OpenAPI указывает, что каждой *операции пути* не
<img src="/img/tutorial/path-operation-configuration/image05.png">
## Резюме
## Резюме { #recap }
Вы можете легко конфигурировать и добавлять метаданные в ваши *операции пути*, передавая параметры *декораторам операций пути*.

View File

@@ -1,8 +1,8 @@
# Path-параметры и валидация числовых данных
# Path-параметры и валидация числовых данных { #path-parameters-and-numeric-validations }
Так же, как с помощью `Query` вы можете добавлять валидацию и метаданные для query-параметров, так и с помощью `Path` вы можете добавлять такую же валидацию и метаданные для path-параметров.
## Импорт Path
## Импорт `Path` { #import-path }
Сначала импортируйте `Path` из `fastapi`, а также импортируйте `Annotated`:
@@ -14,11 +14,11 @@
Если вы используете более старую версию, вы столкнётесь с ошибками при попытке использовать `Annotated`.
Убедитесь, что вы [обновили версию FastAPI](../deployment/versions.md#fastapi_2){.internal-link target=_blank} как минимум до 0.95.1 перед тем, как использовать `Annotated`.
Убедитесь, что вы [обновили версию FastAPI](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} как минимум до 0.95.1 перед тем, как использовать `Annotated`.
///
## Определите метаданные
## Определите метаданные { #declare-metadata }
Вы можете указать все те же параметры, что и для `Query`.
@@ -28,15 +28,11 @@
/// note | Примечание
Path-параметр всегда является обязательным, поскольку он составляет часть пути.
Поэтому следует объявить его с помощью `...`, чтобы обозначить, что этот параметр обязательный.
Тем не менее, даже если вы объявите его как `None` или установите для него значение по умолчанию, это ни на что не повлияет и параметр останется обязательным.
Path-параметр всегда является обязательным, поскольку он должен быть частью пути. Даже если вы объявите его как `None` или зададите значение по умолчанию, это ни на что не повлияет — параметр всё равно будет обязательным.
///
## Задайте нужный вам порядок параметров
## Задайте нужный вам порядок параметров { #order-the-parameters-as-you-need }
/// tip | Подсказка
@@ -58,25 +54,13 @@ Path-параметр всегда является обязательным, п
Поэтому вы можете определить функцию так:
//// tab | Python 3.8 без Annotated
{* ../../docs_src/path_params_numeric_validations/tutorial002.py hl[7] *}
/// tip | Подсказка
Но имейте в виду, что если вы используете `Annotated`, вы не столкнётесь с этой проблемой, так как вы не используете значения по умолчанию параметров функции для `Query()` или `Path()`.
Рекомендуется использовать версию с `Annotated` если возможно.
{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py *}
///
```Python hl_lines="7"
{!> ../../docs_src/path_params_numeric_validations/tutorial002.py!}
```
////
Но имейте в виду, что если вы используете `Annotated`, вы не столкнётесь с этой проблемой, так как вы не используете `Query()` или `Path()` в качестве значения по умолчанию для параметра функции.
{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py hl[10] *}
## Задайте нужный вам порядок параметров, полезные приёмы
## Задайте нужный вам порядок параметров, полезные приёмы { #order-the-parameters-as-you-need-tricks }
/// tip | Подсказка
@@ -97,25 +81,25 @@ Path-параметр всегда является обязательным, п
Передайте `*` в качестве первого параметра функции.
Python не будет ничего делать с `*`, но он будет знать, что все следующие параметры являются именованными аргументами (парами ключ-значение), также известными как <abbr title="From: K-ey W-ord Arg-uments"><code>kwargs</code></abbr>, даже если у них нет значений по умолчанию.
Python не будет ничего делать с `*`, но он будет знать, что все следующие параметры являются именованными аргументами (парами ключ-значение), также известными как <abbr title="От: K-ey W-ord Arg-uments"><code>kwargs</code></abbr>, даже если у них нет значений по умолчанию.
{* ../../docs_src/path_params_numeric_validations/tutorial003.py hl[7] *}
### Лучше с `Annotated`
### Лучше с `Annotated` { #better-with-annotated }
Имейте в виду, что если вы используете `Annotated`, то, поскольку вы не используете значений по умолчанию для параметров функции, то у вас не возникнет подобной проблемы и вам не придётся использовать `*`.
Имейте в виду, что если вы используете `Annotated`, то, поскольку вы не используете значений по умолчанию для параметров функции, у вас не возникнет подобной проблемы и вам, вероятно, не придётся использовать `*`.
{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py39.py hl[10] *}
## Валидация числовых данных: больше или равно
## Валидация числовых данных: больше или равно { #number-validations-greater-than-or-equal }
С помощью `Query` и `Path` (и других классов, которые мы разберём позже) вы можете добавлять ограничения для числовых данных.
В этом примере при указании `ge=1`, параметр `item_id` должен быть больше или равен `1` ("`g`reater than or `e`qual").
В этом примере при указании `ge=1`, параметр `item_id` должен быть целым числом "`g`reater than or `e`qual" — больше или равно `1`.
{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py39.py hl[10] *}
## Валидация числовых данных: больше и меньше или равно
## Валидация числовых данных: больше и меньше или равно { #number-validations-greater-than-and-less-than-or-equal }
То же самое применимо к:
@@ -124,19 +108,19 @@ Python не будет ничего делать с `*`, но он будет з
{* ../../docs_src/path_params_numeric_validations/tutorial005_an_py39.py hl[10] *}
## Валидация числовых данных: числа с плавающей точкой, больше и меньше
## Валидация числовых данных: числа с плавающей точкой, больше и меньше { #number-validations-floats-greater-than-and-less-than }
Валидация также применима к значениям типа `float`.
В этом случае становится важной возможность добавить ограничение <abbr title="greater than"><code>gt</code></abbr>, вместо <abbr title="greater than or equal"><code>ge</code></abbr>, поскольку в таком случае вы можете, например, создать ограничение, чтобы значение было больше `0`, даже если оно меньше `1`.
В этом случае становится важной возможность добавить ограничение <abbr title="greater than больше чем"><code>gt</code></abbr>, вместо <abbr title="greater than or equal больше или равно"><code>ge</code></abbr>, поскольку в таком случае вы можете, например, создать ограничение, чтобы значение было больше `0`, даже если оно меньше `1`.
Таким образом, `0.5` будет корректным значением. А `0.0` или `0` — нет.
То же самое справедливо и для <abbr title="less than"><code>lt</code></abbr>.
То же самое справедливо и для <abbr title="less than меньше чем"><code>lt</code></abbr>.
{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py39.py hl[13] *}
## Резюме
## Резюме { #recap }
С помощью `Query`, `Path` (и других классов, которые мы пока не затронули) вы можете добавлять метаданные и строковую валидацию тем же способом, как и в главе [Query-параметры и валидация строк](query-params-str-validations.md){.internal-link target=_blank}.
@@ -149,7 +133,7 @@ Python не будет ничего делать с `*`, но он будет з
/// info | Информация
`Query`, `Path` и другие классы, которые мы разберём позже, являются наследниками общего класса `Param`.
`Query`, `Path` и другие классы, которые вы разберёте позже, являются наследниками общего класса `Param`.
Все они используют те же параметры для дополнительной валидации и метаданных, которые вы видели ранее.

View File

@@ -1,4 +1,4 @@
# Path-параметры
# Path-параметры { #path-parameters }
Вы можете определить "параметры" или "переменные" пути, используя синтаксис форматированных строк Python:
@@ -12,9 +12,9 @@
{"item_id":"foo"}
```
## Параметры пути с типами
## Параметры пути с типами { #path-parameters-with-types }
Вы можете объявить тип параметра пути в функции, используя стандартные аннотации типов Python.
Вы можете объявить тип параметра пути в функции, используя стандартные аннотации типов Python:
{* ../../docs_src/path_params/tutorial002.py hl[7] *}
@@ -22,11 +22,11 @@
/// check | Заметка
Это обеспечит поддержку редактора внутри функции (проверка ошибок, автодополнение и т.п.).
Это обеспечит поддержку редактора кода внутри функции (проверка ошибок, автозавершение и т.п.).
///
## <abbr title="Или сериализация, парсинг">Преобразование</abbr> данных
## <abbr title="также известное как: сериализация, парсинг, маршаллинг">Преобразование</abbr> данных { #data-conversion }
Если запустите этот пример и перейдёте по адресу: <a href="http://127.0.0.1:8000/items/3" class="external-link" target="_blank">http://127.0.0.1:8000/items/3</a>, то увидите ответ:
@@ -38,44 +38,45 @@
Обратите внимание на значение `3`, которое получила (и вернула) функция. Это целочисленный Python `int`, а не строка `"3"`.
Используя определения типов, **FastAPI** выполняет автоматический <abbr title="преобразование строк из HTTP-запроса в типы данных Python">"парсинг"</abbr> запросов.
Используя такое объявление типов, **FastAPI** выполняет автоматический <abbr title="преобразование строк из HTTP-запроса в типы данных Python">"парсинг"</abbr> запросов.
///
## <abbr title="Или валидация">Проверка</abbr> данных
## Валидация данных { #data-validation }
Если откроете браузер по адресу <a href="http://127.0.0.1:8000/items/foo" class="external-link" target="_blank">http://127.0.0.1:8000/items/foo</a>, то увидите интересную HTTP-ошибку:
```JSON
{
"detail": [
{
"loc": [
"path",
"item_id"
],
"msg": "value is not a valid integer",
"type": "type_error.integer"
}
]
"detail": [
{
"type": "int_parsing",
"loc": [
"path",
"item_id"
],
"msg": "Input should be a valid integer, unable to parse string as an integer",
"input": "foo"
}
]
}
```
из-за того, что параметр пути `item_id` имеет значение `"foo"`, которое не является типом `int`.
из-за того, что параметр пути `item_id` имеет значение `"foo"`, которое не является типом `int`.
Та же ошибка возникнет, если вместо `int` передать `float` , например: <a href="http://127.0.0.1:8000/items/4.2" class="external-link" target="_blank">http://127.0.0.1:8000/items/4.2</a>
Та же ошибка возникнет, если вместо `int` передать `float`, например: <a href="http://127.0.0.1:8000/items/4.2" class="external-link" target="_blank">http://127.0.0.1:8000/items/4.2</a>
/// check | Заметка
**FastAPI** обеспечивает проверку типов, используя всё те же определения типов.
**FastAPI** обеспечивает валидацию данных, используя всё те же определения типов.
Обратите внимание, что в тексте ошибки явно указано место не прошедшее проверку.
Обратите внимание, что в тексте ошибки явно указано место, не прошедшее проверку.
Это очень полезно при разработке и отладке кода, который взаимодействует с API.
///
## Документация
## Документация { #documentation }
И теперь, когда откроете браузер по адресу: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>, то увидите вот такую автоматически сгенерированную документацию API:
@@ -89,17 +90,17 @@
///
## Преимущества стандартизации, альтернативная документация
## Преимущества стандартизации, альтернативная документация { #standards-based-benefits-alternative-documentation }
Поскольку сгенерированная схема соответствует стандарту <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md" class="external-link" target="_blank">OpenAPI</a>, её можно использовать со множеством совместимых инструментов.
Поскольку сгенерированная схема соответствует стандарту <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md" class="external-link" target="_blank">OpenAPI</a>, её можно использовать со множеством совместимых инструментов.
Именно поэтому, FastAPI сам предоставляет альтернативную документацию API (используя ReDoc), которую можно получить по адресу: <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>.
Именно поэтому, **FastAPI** сам предоставляет альтернативную документацию API (используя ReDoc), которую можно получить по адресу: <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>.
<img src="/img/tutorial/path-params/image02.png">
По той же причине, есть множество совместимых инструментов, включая инструменты генерации кода для многих языков.
## Pydantic
## Pydantic { #pydantic }
Вся проверка данных выполняется под капотом с помощью <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a>. Поэтому вы можете быть уверены в качестве обработки данных.
@@ -107,7 +108,7 @@
Некоторые из них рассматриваются в следующих главах данного руководства.
## Порядок имеет значение
## Порядок имеет значение { #order-matters }
При создании *операций пути* можно столкнуться с ситуацией, когда путь является фиксированным.
@@ -117,7 +118,6 @@
Поскольку *операции пути* выполняются в порядке их объявления, необходимо, чтобы путь для `/users/me` был объявлен раньше, чем путь для `/users/{user_id}`:
{* ../../docs_src/path_params/tutorial003.py hl[6,11] *}
Иначе путь для `/users/{user_id}` также будет соответствовать `/users/me`, "подразумевая", что он получает параметр `user_id` со значением `"me"`.
@@ -128,11 +128,11 @@
Первый будет выполняться всегда, так как путь совпадает первым.
## Предопределенные значения
## Предопределенные значения { #predefined-values }
Что если нам нужно заранее определить допустимые *параметры пути*, которые *операция пути* может принимать? В таком случае можно использовать стандартное перечисление <abbr title="Enumeration">`Enum`</abbr> Python.
### Создание класса `Enum`
### Создание класса `Enum` { #create-an-enum-class }
Импортируйте `Enum` и создайте подкласс, который наследуется от `str` и `Enum`.
@@ -150,33 +150,33 @@
/// tip | Подсказка
Если интересно, то "AlexNet", "ResNet" и "LeNet" - это названия <abbr title="Технически, это архитектуры моделей глубокого обучения">моделей</abbr> машинного обучения.
Если интересно, то "AlexNet", "ResNet" и "LeNet" - это названия <abbr title="Технически, архитектуры моделей глубокого обучения">моделей</abbr> Машинного обучения.
///
### Определение *параметра пути*
### Определение *параметра пути* { #declare-a-path-parameter }
Определите *параметр пути*, используя в аннотации типа класс перечисления (`ModelName`), созданный ранее:
{* ../../docs_src/path_params/tutorial005.py hl[16] *}
### Проверьте документацию
### Проверьте документацию { #check-the-docs }
Поскольку доступные значения *параметра пути* определены заранее, интерактивная документация может наглядно их отображать:
<img src="/img/tutorial/path-params/image03.png">
### Работа с *перечислениями* в Python
### Работа с *перечислениями* в Python { #working-with-python-enumerations }
Значение *параметра пути* будет *элементом перечисления*.
#### Сравнение *элементов перечисления*
#### Сравнение *элементов перечисления* { #compare-enumeration-members }
Вы можете сравнить это значение с *элементом перечисления* класса `ModelName`:
{* ../../docs_src/path_params/tutorial005.py hl[17] *}
#### Получение *значения перечисления*
#### Получение *значения перечисления* { #get-the-enumeration-value }
Можно получить фактическое значение (в данном случае - `str`) с помощью `model_name.value` или в общем случае `your_enum_member.value`:
@@ -188,7 +188,7 @@
///
#### Возврат *элементов перечисления*
#### Возврат *элементов перечисления* { #return-enumeration-members }
Из *операции пути* можно вернуть *элементы перечисления*, даже вложенные в тело JSON (например в `dict`).
@@ -204,7 +204,7 @@
}
```
## Path-параметры, содержащие пути
## Path-параметры, содержащие пути { #path-parameters-containing-paths }
Предположим, что есть *операция пути* с путем `/files/{file_path}`.
@@ -212,7 +212,7 @@
Тогда URL для этого файла будет такой: `/files/home/johndoe/myfile.txt`.
### Поддержка OpenAPI
### Поддержка OpenAPI { #openapi-support }
OpenAPI не поддерживает способов объявления *параметра пути*, содержащего внутри *путь*, так как это может привести к сценариям, которые сложно определять и тестировать.
@@ -220,7 +220,7 @@ OpenAPI не поддерживает способов объявления *п
Документация по-прежнему будет работать, хотя и не добавит никакой информации о том, что параметр должен содержать путь.
### Конвертер пути
### Конвертер пути { #path-convertor }
Благодаря одной из опций Starlette, можете объявить *параметр пути*, содержащий *путь*, используя URL вроде:
@@ -242,13 +242,14 @@ OpenAPI не поддерживает способов объявления *п
///
## Резюме
## Резюме { #recap }
Используя **FastAPI** вместе со стандартными объявлениями типов Python (короткими и интуитивно понятными), вы получаете:
* Поддержку редактора (проверку ошибок, автозаполнение и т.п.)
* Поддержку редактора кода (проверку ошибок, автозавершение и т.п.)
* "<abbr title="преобразование строк из HTTP-запроса в типы данных Python">Парсинг</abbr>" данных
* Валидацию данных
* Автоматическую документацию API с указанием типов параметров.
* Аннотации API и автоматическую документацию
И объявлять типы достаточно один раз.

View File

@@ -1,4 +1,4 @@
# Модели Query-Параметров
# Модели Query-Параметров { #query-parameter-models }
Если у вас есть группа связанных **query-параметров**, то вы можете объединить их в одну **Pydantic-модель**.
@@ -10,7 +10,7 @@
///
## Pydantic-Модель для Query-Параметров
## Pydantic-Модель для Query-Параметров { #query-parameters-with-a-pydantic-model }
Объявите нужные **query-параметры** в **Pydantic-модели**, а после аннотируйте параметр как `Query`:
@@ -18,7 +18,7 @@
**FastAPI извлечёт** данные соответствующие **каждому полю модели** из **query-параметров** запроса и выдаст вам объявленную Pydantic-модель заполненную ими.
## Проверьте Сгенерированную Документацию
## Проверьте Сгенерированную Документацию { #check-the-docs }
Вы можете посмотреть query-параметры в графическом интерфейсе сгенерированной документации по пути `/docs`:
@@ -26,7 +26,7 @@
<img src="/img/tutorial/query-param-models/image01.png">
</div>
## Запретить Дополнительные Query-Параметры
## Запретить Дополнительные Query-Параметры { #forbid-extra-query-parameters }
В некоторых случаях (не особо часто встречающихся) вам может понадобиться **ограничить** query-параметры, которые вы хотите получить.
@@ -57,12 +57,12 @@ https://example.com/items/?limit=10&tool=plumbus
}
```
## Заключение
## Заключение { #summary }
Вы можете использовать **Pydantic-модели** для объявления **query-параметров** в **FastAPI**. 😎
/// tip | Совет
Спойлер: вы также можете использовать Pydantic-модели для группировки кук (cookies) и заголовков (headers), но об этом вы прочитаете позже. 🤫
Спойлер: вы также можете использовать Pydantic-модели, чтобы объявлять cookies и HTTP-заголовки, но об этом вы прочитаете позже. 🤫
///

View File

@@ -1,61 +1,51 @@
# Query-параметры и валидация строк
# Query-параметры и валидация строк { #query-parameters-and-string-validations }
**FastAPI** позволяет определять дополнительную информацию и валидацию для ваших параметров.
**FastAPI** позволяет определять дополнительную информацию и выполнять валидацию для ваших параметров.
Давайте рассмотрим следующий пример:
Рассмотрим это приложение в качестве примера:
{* ../../docs_src/query_params_str_validations/tutorial001_py310.py hl[7] *}
Query-параметр `q` имеет тип `Union[str, None]` (или `str | None` в Python 3.10). Это означает, что входной параметр будет типа `str`, но может быть и `None`. Ещё параметр имеет значение по умолчанию `None`, из-за чего FastAPI определит параметр как необязательный.
Query-параметр `q` имеет тип `str | None`, это означает, что он имеет тип `str`, но также может быть `None`. Значение по умолчанию действительно `None`, поэтому FastAPI будет знать, что он не обязателен.
/// note | Технические детали
FastAPI определит параметр `q` как необязательный, потому что его значение по умолчанию `= None`.
FastAPI поймёт, что значение `q` не обязательно, из‑за значения по умолчанию `= None`.
`Union` в `Union[str, None]` позволит редактору кода оказать вам лучшую поддержку и найти ошибки.
Аннотация `str | None` позволит вашему редактору кода обеспечить лучшую поддержку и находить ошибки.
///
## Расширенная валидация
## Дополнительная валидация { #additional-validation }
Добавим дополнительное условие валидации параметра `q` - **длина строки не более 50 символов** (условие проверяется всякий раз, когда параметр `q` не является `None`).
Мы собираемся добавить ограничение: хотя `q` и необязателен, когда он передан, **его длина не должна превышать 50 символов**.
### Импорт `Query` и `Annotated`
### Импорт `Query` и `Annotated` { #import-query-and-annotated }
Чтобы достичь этого, первым делом нам нужно импортировать:
Чтобы сделать это, сначала импортируйте:
* `Query` из пакета `fastapi`:
* `Annotated` из пакета `typing` (или из `typing_extensions` для Python ниже 3.9)
* `Query` из `fastapi`
* `Annotated` из `typing`
//// tab | Python 3.10+
{* ../../docs_src/query_params_str_validations/tutorial002_an_py310.py hl[1,3] *}
В Python 3.9 или выше, `Annotated` является частью стандартной библиотеки, таким образом вы можете импортировать его из `typing`.
/// info | Дополнительная информация
```Python hl_lines="1 3"
{!> ../../docs_src/query_params_str_validations/tutorial002_an_py310.py!}
```
Поддержка `Annotated` (и рекомендация использовать его) появилась в FastAPI версии 0.95.0.
////
Если у вас более старая версия, при попытке использовать `Annotated` вы получите ошибки.
//// tab | Python 3.8+
Убедитесь, что вы [обновили версию FastAPI](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} как минимум до 0.95.1 перед использованием `Annotated`.
В версиях Python ниже Python 3.9 `Annotation` импортируется из `typing_extensions`.
///
Эта библиотека будет установлена вместе с FastAPI.
## Использовать `Annotated` в типе для параметра `q` { #use-annotated-in-the-type-for-the-q-parameter }
```Python hl_lines="3-4"
{!> ../../docs_src/query_params_str_validations/tutorial002_an.py!}
```
Помните, я уже говорил, что `Annotated` можно использовать для добавления метаданных к параметрам в разделе [Введение в типы Python](../python-types.md#type-hints-with-metadata-annotations){.internal-link target=_blank}?
////
Пришло время использовать его с FastAPI. 🚀
## `Annotated` как тип для query-параметра `q`
Помните, как ранее я говорил об Annotated? Он может быть использован для добавления метаданных для ваших параметров в разделе [Введение в аннотации типов Python](../python-types.md#type-hints-with-metadata-annotations){.internal-link target=_blank}?
Пришло время использовать их в FastAPI. 🚀
У нас была аннотация следующего типа:
У нас была такая аннотация типа:
//// tab | Python 3.10+
@@ -73,7 +63,7 @@ q: Union[str, None] = None
////
Вот что мы получим, если обернём это в `Annotated`:
Мы «обернём» это в `Annotated`, и получится:
//// tab | Python 3.10+
@@ -91,173 +81,161 @@ q: Annotated[Union[str, None]] = None
////
Обе эти версии означают одно и тоже. `q` - это параметр, который может быть `str` или `None`, и по умолчанию он будет принимать `None`.
Обе версии означают одно и то же: `q` параметр, который может быть `str` или `None`, и по умолчанию равен `None`.
Давайте повеселимся. 🎉
А теперь к самому интересному. 🎉
## Добавим `Query` в `Annotated` для query-параметра `q`
## Добавим `Query` в `Annotated` для параметра `q` { #add-query-to-annotated-in-the-q-parameter }
Теперь, когда у нас есть `Annotated`, где мы можем добавить больше метаданных, добавим `Query` со значением параметра `max_length` равным 50:
Теперь, когда у нас есть `Annotated`, куда можно поместить дополнительную информацию (в нашем случае — дополнительные правила валидации), добавим `Query` внутрь `Annotated` и установим параметр `max_length` равным `50`:
{* ../../docs_src/query_params_str_validations/tutorial002_an_py310.py hl[9] *}
Обратите внимание, что значение по умолчанию всё ещё `None`, так что параметр остаётся необязательным.
Обратите внимание, что значение по умолчанию по‑прежнему `None`, то есть параметр остаётся необязательным.
Однако теперь, имея `Query(max_length=50)` внутри `Annotated`, мы говорим FastAPI, что мы хотим извлечь это значение из параметров query-запроса (что произойдёт в любом случае 🤷), и что мы хотим иметь **дополнительные условия валидации** для этого значения (для чего мы и делаем это - чтобы получить дополнительную валидацию). 😎
Но теперь, добавив `Query(max_length=50)` внутрь `Annotated`, мы говорим FastAPI, что этому значению нужна **дополнительная валидация** — максимум 50 символов. 😎
Теперь FastAPI:
/// tip | Совет
* **Валидирует** (проверяет), что полученные данные состоят максимум из 50 символов
* Показывает **исчерпывающую ошибку** (будет описание местонахождения ошибки и её причины) для клиента в случаях, когда данные не валидны
* **Задокументирует** параметр в схему OpenAPI *операции пути* (что будет отображено в **UI автоматической документации**)
## Альтернативный (устаревший) способ задать `Query` как значение по умолчанию
В предыдущих версиях FastAPI (ниже <abbr title="ранее 2023-03">0.95.0</abbr>) необходимо было использовать `Query` как значение по умолчанию для query-параметра. Так было вместо размещения его в `Annotated`, так что велика вероятность, что вам встретится такой код. Сейчас объясню.
/// tip | Подсказка
При написании нового кода и везде где это возможно, используйте `Annotated`, как было описано ранее. У этого способа есть несколько преимуществ (о них дальше) и никаких недостатков. 🍰
Здесь мы используем `Query()`, потому что это **query-параметр**. Позже мы увидим другие — `Path()`, `Body()`, `Header()` и `Cookie()`, — они также принимают те же аргументы, что и `Query()`.
///
Вот как вы могли бы использовать `Query()` в качестве значения по умолчанию параметра вашей функции, установив для параметра `max_length` значение 50:
Теперь FastAPI будет:
* **валидировать** данные, удостоверяясь, что максимальная длина — 50 символов;
* показывать **понятную ошибку** клиенту, если данные невалидны;
* **документировать** параметр в *операции пути* схемы OpenAPI (он будет показан в **UI автоматической документации**).
## Альтернатива (устаревшее): `Query` как значение по умолчанию { #alternative-old-query-as-the-default-value }
В предыдущих версиях FastAPI (до <abbr title="до 2023-03">0.95.0</abbr>) требовалось использовать `Query` как значение по умолчанию для параметра вместо помещения его в `Annotated`. Скорее всего вы ещё встретите такой код, поэтому поясню.
/// tip | Подсказка
Для нового кода и везде, где это возможно, используйте `Annotated`, как описано выше. У этого есть несколько преимуществ (см. ниже) и нет недостатков. 🍰
///
Вот как можно использовать `Query()` как значение по умолчанию для параметра функции, установив `max_length` равным 50:
{* ../../docs_src/query_params_str_validations/tutorial002_py310.py hl[7] *}
В таком случае (без использования `Annotated`), мы заменили значение по умолчанию с `None` на `Query()` в функции. Теперь нам нужно установить значение по умолчанию для query-параметра `Query(default=None)`, что необходимо для тех же целей, как когда ранее просто указывалось значение по умолчанию (по крайней мере, для FastAPI).
Так как в этом случае (без `Annotated`) мы заменяем в функции значение по умолчанию `None` на `Query()`, теперь нужно указать значение по умолчанию через параметр `Query(default=None)`, это служит той же цели — задать значение по умолчанию (по крайней мере для FastAPI).
Таким образом:
```Python
q: Union[str, None] = Query(default=None)
```
...делает параметр необязательным со значением по умолчанию `None`, также как это делает:
```Python
q: Union[str, None] = None
```
И для Python 3.10 и выше:
Итак:
```Python
q: str | None = Query(default=None)
```
...делает параметр необязательным со значением по умолчанию `None`, также как это делает:
...делает параметр необязательным со значением по умолчанию `None`, так же как:
```Python
q: str | None = None
```
Но он явно объявляет его как query-параметр.
Но вариант с `Query` явно объявляет его как query-параметр.
/// info | Дополнительная информация
Запомните, важной частью объявления параметра как необязательного является:
Затем мы можем передать и другие параметры в `Query`. В данном случае — параметр `max_length`, применимый к строкам:
```Python
= None
q: str | None = Query(default=None, max_length=50)
```
или:
Это провалидирует данные, покажет понятную ошибку, если данные невалидны, и задокументирует параметр в *операции пути* схемы OpenAPI.
```Python
= Query(default=None)
```
### `Query` как значение по умолчанию или внутри `Annotated` { #query-as-the-default-value-or-in-annotated }
так как `None` указан в качестве значения по умолчанию, параметр будет **необязательным**.
Помните, что при использовании `Query` внутри `Annotated` нельзя указывать параметр `default` у `Query`.
`Union[str, None]` позволит редактору кода оказать вам лучшую поддержку. Но это не то, на что обращает внимание FastAPI для определения необязательности параметра.
Вместо этого используйте обычное значение по умолчанию параметра функции. Иначе это будет неоднозначно.
///
Теперь, мы можем указать больше параметров для `Query`. В данном случае, параметр `max_length` применяется к строкам:
```Python
q: Union[str, None] = Query(default=None, max_length=50)
```
Входные данные будут проверены. Если данные недействительны, тогда будет указано на ошибку в запросе (будет описание местонахождения ошибки и её причины). Кроме того, параметр задокументируется в схеме OpenAPI данной *операции пути*.
### Использовать `Query` как значение по умолчанию или добавить в `Annotated`
Когда `Query` используется внутри `Annotated`, вы не можете использовать параметр `default` у `Query`.
Вместо этого, используйте обычное указание значения по умолчанию для параметра функции. Иначе, это будет несовместимо.
Следующий пример не рабочий:
Например, так делать нельзя:
```Python
q: Annotated[str, Query(default="rick")] = "morty"
```
...потому что нельзя однозначно определить, что именно должно быть значением по умолчанию: `"rick"` или `"morty"`.
...потому что непонятно, какое значение должно быть по умолчанию: `"rick"` или `"morty"`.
Вам следует использовать (предпочтительно):
Следовательно, используйте (предпочтительно):
```Python
q: Annotated[str, Query()] = "rick"
```
...или как в старом коде, который вам может попасться:
...или в старой кодовой базе вы увидите:
```Python
q: str = Query(default="rick")
```
### Преимущества `Annotated`
### Преимущества `Annotated` { #advantages-of-annotated }
**Рекомендуется использовать `Annotated`** вместо значения по умолчанию в параметрах функции, потому что так **лучше** по нескольким причинам. 🤓
**Рекомендуется использовать `Annotated`** вместо задания значения по умолчанию в параметрах функции так **лучше** по нескольким причинам. 🤓
Значение **по умолчанию** у **параметров функции** - это **действительно значение по умолчанию**, что более интуитивно понятно для пользователей Python. 😌
**Значение по умолчанию** у **параметра функции** это **настоящее значение по умолчанию**, что более интуитивно для Python. 😌
Вы можете **вызвать** ту же функцию в **иных местах** без FastAPI, и она **сработает как ожидается**. Если это **обязательный** параметр (без значения по умолчанию), ваш **редактор кода** сообщит об ошибке. **Python** также укажет на ошибку, если вы вызовете функцию без передачи ей обязательного параметра.
Вы можете **вызвать** эту же функцию в **других местах** без FastAPI, и она будет **работать как ожидается**. Если есть **обязательный** параметр (без значения по умолчанию), ваш **редактор кода** сообщит об ошибке, **Python** тоже пожалуется, если вы запустите её без передачи обязательного параметра.
Если вы вместо `Annotated` используете **(устаревший) стиль значений по умолчанию**, тогда при вызове этой функции без FastAPI в **другом месте** вам необходимо **помнить** о передаче аргументов функции, чтобы она работала корректно. В противном случае, значения будут отличаться от тех, что вы ожидаете (например, `QueryInfo` или что-то подобное вместо `str`). И ни ваш редактор кода, ни Python не будут жаловаться на работу этой функции, только когда вычисления внутри дадут сбой.
Если вы не используете `Annotated`, а применяете **(устаревший) стиль со значением по умолчанию**, то при вызове этой функции без FastAPI в **других местах** вам нужно **помнить** о том, что надо передать аргументы, чтобы всё работало корректно, иначе значения будут не такими, как вы ожидаете (например, вместо `str` будет `QueryInfo` или что-то подобное). И ни редактор, ни Python не будут ругаться при самом вызове функции — ошибка проявится лишь при операциях внутри.
Так как `Annotated` может принимать более одной аннотации метаданных, то теперь вы можете использовать ту же функцию с другими инструментами, например <a href="https://typer.tiangolo.com/" class="external-link" target="_blank">Typer</a>. 🚀
Так как `Annotated` может содержать больше одной аннотации метаданных, теперь вы можете использовать ту же функцию и с другими инструментами, например с <a href="https://typer.tiangolo.com/" class="external-link" target="_blank">Typer</a>. 🚀
## Больше валидации
## Больше валидаций { #add-more-validations }
Вы также можете добавить параметр `min_length`:
Можно также добавить параметр `min_length`:
{* ../../docs_src/query_params_str_validations/tutorial003_an_py310.py hl[10] *}
## Регулярные выражения
## Регулярные выражения { #add-regular-expressions }
Вы можете определить <abbr title="Регулярное выражение, regex или regexp - это последовательность символов, определяющая шаблон для строк.">регулярное выражение</abbr>, которому должен соответствовать параметр:
Вы можете определить <abbr title="Регулярное выражение (regex, regexp) — это последовательность символов, задающая шаблон поиска для строк.">регулярное выражение</abbr> `pattern`, которому должен соответствовать параметр:
{* ../../docs_src/query_params_str_validations/tutorial004_an_py310.py hl[11] *}
Данное регулярное выражение проверяет, что полученное значение параметра:
Данный шаблон регулярного выражения проверяет, что полученное значение параметра:
* `^`: начало строки.
* `fixedquery`: в точности содержит строку `fixedquery`.
* `$`: конец строки, не имеет символов после `fixedquery`.
* `^`: начинается с следующих символов, до них нет символов.
* `fixedquery`: имеет точное значение `fixedquery`.
* `$`: заканчивается здесь, после `fixedquery` нет никаких символов.
Не переживайте, если **"регулярное выражение"** вызывает у вас трудности. Это достаточно сложная тема для многих людей. Вы можете сделать множество вещей без использования регулярных выражений.
Если вы теряетесь во всех этих идеях про **«регулярные выражения»**, не переживайте. Это сложная тема для многих. Многое можно сделать и без них.
Но когда они вам понадобятся, и вы закончите их освоение, то не будет проблемой использовать их в **FastAPI**.
Теперь вы знаете, что когда они понадобятся, вы сможете использовать их в **FastAPI**.
## Значения по умолчанию
### `regex` из Pydantic v1 вместо `pattern` { #pydantic-v1-regex-instead-of-pattern }
Вы точно также можете указать любое значение `по умолчанию`, как ранее указывали `None`.
До Pydantic версии 2 и до FastAPI 0.100.0 этот параметр назывался `regex`, а не `pattern`, но сейчас он устарел.
Например, вы хотите для параметра запроса `q` указать, что он должен состоять минимум из 3 символов (`min_length=3`) и иметь значение по умолчанию `"fixedquery"`:
Вы всё ещё можете встретить такой код:
//// tab | Pydantic v1
{* ../../docs_src/query_params_str_validations/tutorial004_regex_an_py310.py hl[11] *}
////
Имейте в виду, что это устарело, и код следует обновить на использование нового параметра `pattern`. 🤓
## Значения по умолчанию { #default-values }
Конечно, можно использовать и другие значения по умолчанию, не только `None`.
Допустим, вы хотите объявить, что query-параметр `q` должен иметь `min_length` равный `3` и значение по умолчанию `"fixedquery"`:
{* ../../docs_src/query_params_str_validations/tutorial005_an_py39.py hl[9] *}
/// note | Технические детали
/// note | Примечание
Наличие значения по умолчанию делает параметр необязательным.
Наличие значения по умолчанию любого типа, включая `None`, делает параметр необязательным.
///
## Обязательный параметр
## Обязательные параметры { #required-parameters }
Когда вам не требуется дополнительная валидация или дополнительные метаданные для параметра запроса, вы можете сделать параметр `q` обязательным просто не указывая значения по умолчанию. Например:
Когда не требуется объявлять дополнительные проверки или метаданные, можно сделать query-параметр `q` обязательным, просто не указывая значение по умолчанию, например:
```Python
q: str
@@ -266,60 +244,42 @@ q: str
вместо:
```Python
q: Union[str, None] = None
q: str | None = None
```
Но у нас query-параметр определён как `Query`. Например:
//// tab | Annotated
Но сейчас мы объявляем его через `Query`, например так:
```Python
q: Annotated[Union[str, None], Query(min_length=3)] = None
q: Annotated[str | None, Query(min_length=3)] = None
```
////
//// tab | без Annotated
```Python
q: Union[str, None] = Query(default=None, min_length=3)
```
////
В таком случае, чтобы сделать query-параметр `Query` обязательным, вы можете просто не указывать значение по умолчанию:
Поэтому, когда вам нужно объявить значение как обязательное при использовании `Query`, просто не указывайте значение по умолчанию:
{* ../../docs_src/query_params_str_validations/tutorial006_an_py39.py hl[9] *}
### Обязательный параметр с `None`
### Обязательный, но может быть `None` { #required-can-be-none }
Вы можете определить, что параметр может принимать `None`, но всё ещё является обязательным. Это может потребоваться для того, чтобы пользователи явно указали параметр, даже если его значение будет `None`.
Можно объявить, что параметр может принимать `None`, но при этом остаётся обязательным. Это заставит клиентов отправлять значение, даже если это значение `None`.
Чтобы этого добиться, вам нужно определить `None` как валидный тип для параметра запроса, но также указать `default=...`:
Для этого объявите, что `None` — валидный тип, но просто не задавайте значение по умолчанию:
{* ../../docs_src/query_params_str_validations/tutorial006c_an_py310.py hl[9] *}
/// tip | Подсказка
## Query-параметр - список / несколько значений { #query-parameter-list-multiple-values }
Pydantic, мощь которого используется в FastAPI для валидации и сериализации, имеет специальное поведение для `Optional` или `Union[Something, None]` без значения по умолчанию. Вы можете узнать об этом больше в документации Pydantic, раздел <a href="https://docs.pydantic.dev/latest/concepts/models/#required-optional-fields" class="external-link" target="_blank">Обязательные Опциональные поля</a>.
Когда вы явно объявляете query-параметр через `Query`, можно также указать, что он принимает список значений, иначе говоря — несколько значений.
///
## Множество значений для query-параметра
Для query-параметра `Query` можно указать, что он принимает список значений (множество значений).
Например, query-параметр `q` может быть указан в URL несколько раз. И если вы ожидаете такой формат запроса, то можете указать это следующим образом:
Например, чтобы объявить query-параметр `q`, который может встречаться в URL несколько раз, можно написать:
{* ../../docs_src/query_params_str_validations/tutorial011_an_py310.py hl[9] *}
Затем, получив такой URL:
Тогда при таком URL:
```
http://localhost:8000/items/?q=foo&q=bar
```
вы бы получили несколько значений (`foo` и `bar`), которые относятся к параметру `q`, в виде Python `list` внутри вашей *функции обработки пути*, в *параметре функции* `q`.
вы получите множественные значения query-параметра `q` (`foo` и `bar`) в виде Python-`list` внутри вашей *функции обработки пути*, в *параметре функции* `q`.
Таким образом, ответ на этот URL будет:
@@ -332,29 +292,29 @@ http://localhost:8000/items/?q=foo&q=bar
}
```
/// tip | Подсказка
/// tip | Совет
Чтобы объявить query-параметр типом `list`, как в примере выше, вам нужно явно использовать `Query`, иначе он будет интерпретирован как тело запроса.
Чтобы объявить query-параметр типа `list`, как в примере выше, нужно явно использовать `Query`, иначе он будет интерпретирован как тело запроса.
///
Интерактивная документация API будет обновлена соответствующим образом, где будет разрешено множество значений:
Интерактивная документация API обновится соответствующим образом и позволит передавать несколько значений:
<img src="/img/tutorial/query-params-str-validations/image02.png">
### Query-параметр со множеством значений по умолчанию
### Query-параметр - список / несколько значений со значением по умолчанию { #query-parameter-list-multiple-values-with-defaults }
Вы также можете указать тип `list` со списком значений по умолчанию на случай, если вам их не предоставят:
Можно также определить значение по умолчанию как `list`, если ничего не передано:
{* ../../docs_src/query_params_str_validations/tutorial012_an_py39.py hl[9] *}
Если вы перейдёте по ссылке:
Если вы перейдёте по адресу:
```
http://localhost:8000/items/
```
значение по умолчанию для `q` будет: `["foo", "bar"]` и ответом для вас будет:
значение по умолчанию для `q` будет: `["foo", "bar"]`, и ответом будет:
```JSON
{
@@ -365,45 +325,45 @@ http://localhost:8000/items/
}
```
#### Использование `list`
#### Просто `list` { #using-just-list }
Вы также можете использовать `list` напрямую вместо `List[str]` (или `list[str]` в Python 3.9+):
Можно использовать `list` напрямую вместо `list[str]`:
{* ../../docs_src/query_params_str_validations/tutorial013_an_py39.py hl[9] *}
/// note | Технические детали
Запомните, что в таком случае, FastAPI не будет проверять содержимое списка.
Имейте в виду, что в этом случае FastAPI не будет проверять содержимое списка.
Например, для List[int] список будет провалидирован (и задокументирован) на содержание только целочисленных элементов. Но для простого `list` такой проверки не будет.
Например, `list[int]` проверит (и задокументирует), что элементы списка — целые числа. А просто `list` — нет.
///
## Больше метаданных
## Больше метаданных { #declare-more-metadata }
Вы можете добавить больше информации об query-параметре.
Можно добавить больше информации о параметре.
Указанная информация будет включена в генерируемую OpenAPI документацию и использована в пользовательском интерфейсе и внешних инструментах.
Эта информация будет включена в сгенерированную OpenAPI-схему и использована интерфейсами документации и внешними инструментами.
/// note | Технические детали
Имейте в виду, что разные инструменты могут иметь разные уровни поддержки OpenAPI.
Помните, что разные инструменты могут иметь разный уровень поддержки OpenAPI.
Некоторые из них могут не отображать (на данный момент) всю заявленную дополнительную информацию, хотя в большинстве случаев отсутствующая функция уже запланирована к разработке.
Некоторые из них пока могут не показывать всю дополнительную информацию, хотя в большинстве случаев недостающая возможность уже запланирована к разработке.
///
Вы можете указать название query-параметра, используя параметр `title`:
Можно задать `title`:
{* ../../docs_src/query_params_str_validations/tutorial007_an_py310.py hl[10] *}
Добавить описание, используя параметр `description`:
И `description`:
{* ../../docs_src/query_params_str_validations/tutorial008_an_py310.py hl[14] *}
## Псевдонимы параметров
## Псевдонимы параметров { #alias-parameters }
Представьте, что вы хотите использовать query-параметр с названием `item-query`.
Представьте, что вы хотите, чтобы параметр назывался `item-query`.
Например:
@@ -411,54 +371,117 @@ http://localhost:8000/items/
http://127.0.0.1:8000/items/?item-query=foobaritems
```
Но `item-query` является невалидным именем переменной в Python.
Но `item-query` — недопустимое имя переменной в Python.
Наиболее похожее валидное имя `item_query`.
Ближайший вариант — `item_query`.
Но вам всё равно необходим `item-query`...
Но вам всё равно нужно именно `item-query`...
Тогда вы можете объявить `псевдоним`, и этот псевдоним будет использоваться для поиска значения параметра запроса:
Тогда можно объявить `alias`, и этот псевдоним будет использован для поиска значения параметра:
{* ../../docs_src/query_params_str_validations/tutorial009_an_py310.py hl[9] *}
## Устаревшие параметры
## Маркировка параметров как устаревших { #deprecating-parameters }
Предположим, вы больше не хотите использовать какой-либо параметр.
Предположим, этот параметр вам больше не нравится.
Вы решили оставить его, потому что клиенты всё ещё им пользуются. Но вы хотите отобразить это в документации как <abbr title="устарело, не рекомендуется использовать">устаревший функционал</abbr>.
Его нужно оставить на какое‑то время, так как клиенты его используют, но вы хотите, чтобы в документации он явно отображался как <abbr title="устаревший, не рекомендуется использовать">устаревший</abbr>.
Тогда для `Query` укажите параметр `deprecated=True`:
Тогда передайте параметр `deprecated=True` в `Query`:
{* ../../docs_src/query_params_str_validations/tutorial010_an_py310.py hl[19] *}
В документации это будет отображено следующим образом:
В документации это будет показано так:
<img src="/img/tutorial/query-params-str-validations/image01.png">
## Исключить из OpenAPI
## Исключить параметры из OpenAPI { #exclude-parameters-from-openapi }
Чтобы исключить query-параметр из генерируемой OpenAPI схемы (а также из системы автоматической генерации документации), укажите в `Query` параметр `include_in_schema=False`:
Чтобы исключить query-параметр из генерируемой OpenAPI-схемы (и, следовательно, из систем автоматической документации), укажите у `Query` параметр `include_in_schema=False`:
{* ../../docs_src/query_params_str_validations/tutorial014_an_py310.py hl[10] *}
## Резюме
## Кастомная валидация { #custom-validation }
Вы можете объявлять дополнительные правила валидации и метаданные для ваших параметров запроса.
Бывают случаи, когда нужна **кастомная валидация**, которую нельзя выразить параметрами выше.
Общие метаданные:
В таких случаях можно использовать **кастомную функцию-валидатор**, которая применяется после обычной валидации (например, после проверки, что значение — это `str`).
Этого можно добиться, используя <a href="https://docs.pydantic.dev/latest/concepts/validators/#field-after-validator" class="external-link" target="_blank">`AfterValidator` Pydantic</a> внутри `Annotated`.
/// tip | Совет
В Pydantic также есть <a href="https://docs.pydantic.dev/latest/concepts/validators/#field-before-validator" class="external-link" target="_blank">`BeforeValidator`</a> и другие. 🤓
///
Например, эта кастомная проверка убеждается, что ID элемента начинается с `isbn-` для номера книги <abbr title="ISBN означает International Standard Book Number Международный стандартный книжный номер">ISBN</abbr> или с `imdb-` для ID URL фильма на <abbr title="IMDB (Internet Movie Database) — веб‑сайт с информацией о фильмах">IMDB</abbr>:
{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py hl[5,16:19,24] *}
/// info | Дополнительная информация
Это доступно в Pydantic версии 2 и выше. 😎
///
/// tip | Совет
Если вам нужна валидация, требующая общения с каким‑либо **внешним компонентом** — базой данных или другим API — вместо этого используйте **Зависимости FastAPI** (FastAPI Dependencies), вы познакомитесь с ними позже.
Эти кастомные валидаторы предназначены для проверок, которые можно выполнить, имея **только** те же **данные**, что пришли в запросе.
///
### Понимание этого кода { #understand-that-code }
Важный момент — это использовать **`AfterValidator` с функцией внутри `Annotated`**. Смело пропускайте эту часть. 🤸
---
Но если вам любопытен именно этот пример и всё ещё интересно, вот немного подробностей.
#### Строка и `value.startswith()` { #string-with-value-startswith }
Заметили? Метод строки `value.startswith()` может принимать кортеж — тогда будет проверено каждое значение из кортежа:
{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py ln[16:19] hl[17] *}
#### Случайный элемент { #a-random-item }
С помощью `data.items()` мы получаем <abbr title="Объект, по которому можно итерироваться циклом for, например список, множество и т. п.">итерируемый объект</abbr> с кортежами, содержащими ключ и значение для каждого элемента словаря.
Мы превращаем этот итерируемый объект в обычный `list` через `list(data.items())`.
Затем с `random.choice()` можно получить **случайное значение** из списка — то есть кортеж вида `(id, name)`. Это будет что‑то вроде `("imdb-tt0371724", "The Hitchhiker's Guide to the Galaxy")`.
После этого мы **распаковываем** эти два значения кортежа в переменные `id` и `name`.
Так что, если пользователь не передал ID элемента, он всё равно получит случайную рекомендацию.
...и всё это в **одной простой строке**. 🤯 Разве не прекрасен Python? 🐍
{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py ln[22:30] hl[29] *}
## Резюме { #recap }
Вы можете объявлять дополнительные проверки и метаданные для параметров.
Общие метаданные и настройки:
* `alias`
* `title`
* `description`
* `deprecated`
* `include_in_schema`
Специфичные правила валидации для строк:
Проверки, специфичные для строк:
* `min_length`
* `max_length`
* `regex`
* `pattern`
В рассмотренных примерах показано объявление правил валидации для строковых значений `str`.
Кастомные проверки с использованием `AfterValidator`.
В следующих главах вы увидете, как объявлять правила валидации для других типов (например, чисел).
В этих примерах вы видели, как объявлять проверки для значений типа `str`.
Смотрите следующие главы, чтобы узнать, как объявлять проверки для других типов, например чисел.

View File

@@ -1,6 +1,6 @@
# Query-параметры
# Query-параметры { #query-parameters }
Когда вы объявляете параметры функции, которые не являются параметрами пути, они автоматически интерпретируются как "query"-параметры.
Когда вы объявляете параметры функции, которые не являются параметрами пути, они автоматически интерпретируются как "query"-параметры.
{* ../../docs_src/query_params/tutorial001.py hl[9] *}
@@ -19,7 +19,7 @@ http://127.0.0.1:8000/items/?skip=0&limit=10
Будучи частью URL-адреса, они "по умолчанию" являются строками.
Но когда вы объявляете их с использованием аннотаций (в примере выше, как `int`), они конвертируются в указанный тип данных и проходят проверку на соответствие ему.
Но когда вы объявляете их с использованием типов Python (в примере выше, как `int`), они конвертируются в указанный тип данных и проходят проверку на соответствие ему.
Все те же правила, которые применяются к path-параметрам, также применяются и query-параметрам:
@@ -28,7 +28,7 @@ http://127.0.0.1:8000/items/?skip=0&limit=10
* Проверка на соответствие данных (Валидация)
* Автоматическая документация
## Значения по умолчанию
## Значения по умолчанию { #defaults }
Поскольку query-параметры не являются фиксированной частью пути, они могут быть не обязательными и иметь значения по умолчанию.
@@ -57,7 +57,7 @@ http://127.0.0.1:8000/items/?skip=20
* `skip=20`: потому что вы установили это в URL-адресе
* `limit=10`: т.к это было значение по умолчанию
## Необязательные параметры
## Необязательные параметры { #optional-parameters }
Аналогично, вы можете объявлять необязательные query-параметры, установив их значение по умолчанию, равное `None`:
@@ -71,7 +71,7 @@ http://127.0.0.1:8000/items/?skip=20
///
## Преобразование типа параметра запроса
## Преобразование типа параметра запроса { #query-parameter-type-conversion }
Вы также можете объявлять параметры с типом `bool`, которые будут преобразованы соответственно:
@@ -109,10 +109,9 @@ http://127.0.0.1:8000/items/foo?short=yes
или в любом другом варианте написания (в верхнем регистре, с заглавной буквой, и т.п), внутри вашей функции параметр `short` будет иметь значение `True` типа данных `bool` . В противном случае - `False`.
## Смешивание query-параметров и path-параметров { #multiple-path-and-query-parameters }
## Смешивание query-параметров и path-параметров
Вы можете объявлять несколько query-параметров и path-параметров одновременно,**FastAPI** сам разберётся, что чем является.
Вы можете объявлять несколько query-параметров и path-параметров одновременно, **FastAPI** сам разберётся, что чем является.
И вы не обязаны объявлять их в каком-либо определенном порядке.
@@ -120,9 +119,9 @@ http://127.0.0.1:8000/items/foo?short=yes
{* ../../docs_src/query_params/tutorial004_py310.py hl[6,8] *}
## Обязательные query-параметры
## Обязательные query-параметры { #required-query-parameters }
Когда вы объявляете значение по умолчанию для параметра, который не является path-параметром (в этом разделе, мы пока что познакомились только с path-параметрами), то это значение не является обязательным.
Когда вы объявляете значение по умолчанию для параметра, который не является path-параметром (в этом разделе, мы пока что познакомились только с path-параметрами), то он не является обязательным.
Если вы не хотите задавать конкретное значение, но хотите сделать параметр необязательным, вы можете установить значение по умолчанию равным `None`.
@@ -142,16 +141,17 @@ http://127.0.0.1:8000/items/foo-item
```JSON
{
"detail": [
{
"loc": [
"query",
"needy"
],
"msg": "field required",
"type": "value_error.missing"
}
]
"detail": [
{
"type": "missing",
"loc": [
"query",
"needy"
],
"msg": "Field required",
"input": null
}
]
}
```
@@ -170,7 +170,7 @@ http://127.0.0.1:8000/items/foo-item?needy=sooooneedy
}
```
Конечно, вы можете определить некоторые параметры как обязательные, некоторые - со значением по умполчанию, а некоторые - полностью необязательные:
Конечно, вы можете определить некоторые параметры как обязательные, некоторые со значением по умолчанию, а некоторые полностью необязательные:
{* ../../docs_src/query_params/tutorial006_py310.py hl[8] *}
@@ -182,6 +182,6 @@ http://127.0.0.1:8000/items/foo-item?needy=sooooneedy
/// tip | Подсказка
Вы можете использовать класс `Enum` также, как ранее применяли его с [Path-параметрами](path-params.md#_7){.internal-link target=_blank}.
Вы можете использовать класс `Enum` также, как ранее применяли его с [Path-параметрами](path-params.md#predefined-values){.internal-link target=_blank}.
///

View File

@@ -1,4 +1,4 @@
# Загрузка файлов
# Загрузка файлов { #request-files }
Используя класс `File`, мы можем позволить клиентам загружать файлы.
@@ -6,19 +6,23 @@
Чтобы получать загруженные файлы, сначала установите <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>.
Например: `pip install python-multipart`.
Убедитесь, что вы создали [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активировали его, а затем установили пакет, например:
Это связано с тем, что загружаемые файлы передаются как данные формы.
```console
$ pip install python-multipart
```
Это связано с тем, что загружаемые файлы передаются как "данные формы".
///
## Импорт `File`
## Импорт `File` { #import-file }
Импортируйте `File` и `UploadFile` из модуля `fastapi`:
{* ../../docs_src/request_files/tutorial001_an_py39.py hl[3] *}
## Определите параметры `File`
## Определите параметры `File` { #define-file-parameters }
Создайте параметры `File` так же, как вы это делаете для `Body` или `Form`:
@@ -46,7 +50,7 @@
Однако возможны случаи, когда использование `UploadFile` может оказаться полезным.
## Загрузка файла с помощью `UploadFile`
## Параметры файла с `UploadFile` { #file-parameters-with-uploadfile }
Определите параметр файла с типом `UploadFile`:
@@ -62,7 +66,7 @@
* Он реализует <a href="https://docs.python.org/3/glossary.html#term-file-like-object" class="external-link" target="_blank">file-like</a> `async` интерфейс.
* Он предоставляет реальный объект Python <a href="https://docs.python.org/3/library/tempfile.html#tempfile.SpooledTemporaryFile" class="external-link" target="_blank">`SpooledTemporaryFile`</a> который вы можете передать непосредственно другим библиотекам, которые ожидают файл в качестве объекта.
### `UploadFile`
### `UploadFile` { #uploadfile }
`UploadFile` имеет следующие атрибуты:
@@ -70,12 +74,12 @@
* `content_type`: Строка `str` с типом содержимого (MIME type / media type) (например, `image/jpeg`).
* `file`: <a href="https://docs.python.org/3/library/tempfile.html#tempfile.SpooledTemporaryFile" class="external-link" target="_blank">`SpooledTemporaryFile`</a> (a <a href="https://docs.python.org/3/glossary.html#term-file-like-object" class="external-link" target="_blank">file-like</a> объект). Это фактический файл Python, который можно передавать непосредственно другим функциям или библиотекам, ожидающим файл в качестве объекта.
`UploadFile` имеет следующие методы `async`. Все они вызывают соответствующие файловые методы (используя внутренний SpooledTemporaryFile).
`UploadFile` имеет следующие методы `async`. Все они вызывают соответствующие файловые методы (используя внутренний `SpooledTemporaryFile`).
* `write(data)`: Записать данные `data` (`str` или `bytes`) в файл.
* `read(size)`: Прочитать количество `size` (`int`) байт/символов из файла.
* `seek(offset)`: Перейти к байту на позиции `offset` (`int`) в файле.
* Наример, `await myfile.seek(0)` перейдет к началу файла.
* Например, `await myfile.seek(0)` перейдет к началу файла.
* Это особенно удобно, если вы один раз выполнили команду `await myfile.read()`, а затем вам нужно прочитать содержимое файла еще раз.
* `close()`: Закрыть файл.
@@ -93,6 +97,7 @@ contents = await myfile.read()
contents = myfile.file.read()
```
/// note | Технические детали `async`
При использовании методов `async` **FastAPI** запускает файловые методы в пуле потоков и ожидает их.
@@ -105,7 +110,7 @@ contents = myfile.file.read()
///
## Про данные формы ("Form Data")
## Что такое «данные формы» { #what-is-form-data }
Способ, которым HTML-формы (`<form></form>`) отправляют данные на сервер, обычно использует "специальную" кодировку для этих данных, отличную от JSON.
@@ -117,7 +122,7 @@ contents = myfile.file.read()
Но когда форма включает файлы, она кодируется как multipart/form-data. Если вы используете `File`, **FastAPI** будет знать, что ему нужно получить файлы из нужной части тела.
Если вы хотите узнать больше об этих кодировках и полях форм, перейдите по ссылке <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" class="external-link" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> web docs for <code>POST</code></a>.
Если вы хотите узнать больше об этих кодировках и полях форм, перейдите по ссылке <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" class="external-link" target="_blank"><abbr title="Mozilla Developer Network Сеть разработчиков Mozilla">MDN</abbr> web docs for <code>POST</code></a>.
///
@@ -129,19 +134,19 @@ contents = myfile.file.read()
///
## Необязательная загрузка файлов
## Необязательная загрузка файлов { #optional-file-upload }
Вы можете сделать загрузку файла необязательной, используя стандартные аннотации типов и установив значение по умолчанию `None`:
{* ../../docs_src/request_files/tutorial001_02_an_py310.py hl[9,17] *}
## `UploadFile` с дополнительными метаданными
## `UploadFile` с дополнительными метаданными { #uploadfile-with-additional-metadata }
Вы также можете использовать `File()` вместе с `UploadFile`, например, для установки дополнительных метаданных:
{* ../../docs_src/request_files/tutorial001_03_an_py39.py hl[9,15] *}
## Загрузка нескольких файлов
## Загрузка нескольких файлов { #multiple-file-uploads }
Можно одновременно загружать несколько файлов.
@@ -153,7 +158,7 @@ contents = myfile.file.read()
Вы получите, как и было объявлено, список `list` из `bytes` или `UploadFile`.
/// note | Technical Details
/// note | Технические детали
Можно также использовать `from starlette.responses import HTMLResponse`.
@@ -161,12 +166,12 @@ contents = myfile.file.read()
///
### Загрузка нескольких файлов с дополнительными метаданными
### Загрузка нескольких файлов с дополнительными метаданными { #multiple-file-uploads-with-additional-metadata }
Так же, как и раньше, вы можете использовать `File()` для задания дополнительных параметров, даже для `UploadFile`:
{* ../../docs_src/request_files/tutorial003_an_py39.py hl[11,18:20] *}
## Резюме
## Резюме { #recap }
Используйте `File`, `bytes` и `UploadFile` для работы с файлами, которые будут загружаться и передаваться в виде данных формы.

View File

@@ -1,6 +1,6 @@
# Модели форм
# Модели форм { #form-models }
Вы можете использовать **Pydantic-модели** для объявления **полей форм** в FastAPI.
Вы можете использовать **Pydantic-модели** для объявления **полей формы** в FastAPI.
/// info | Дополнительная информация
@@ -16,11 +16,11 @@ $ pip install python-multipart
/// note | Заметка
Этот функционал доступен с версии `0.113.0`. 🤓
Этот функционал доступен начиная с версии FastAPI `0.113.0`. 🤓
///
## Pydantic-модель для формы
## Pydantic-модели для форм { #pydantic-models-for-forms }
Вам просто нужно объявить **Pydantic-модель** с полями, которые вы хотите получить как **поля формы**, а затем объявить параметр как `Form`:
@@ -28,21 +28,21 @@ $ pip install python-multipart
**FastAPI** **извлечёт** данные для **каждого поля** из **данных формы** в запросе и выдаст вам объявленную Pydantic-модель.
## Проверка сгенерированной документации
## Проверьте документацию { #check-the-docs }
Вы можете посмотреть поля формы в графическом интерфейсе Документации по пути `/docs`:
Вы можете проверить это в интерфейсе документации по адресу `/docs`:
<div class="screenshot">
<img src="/img/tutorial/request-form-models/image01.png">
</div>
## Запрет дополнительных полей формы
## Запрет дополнительных полей формы { #forbid-extra-form-fields }
В некоторых случаях (не особо часто встречающихся) вам может понадобиться **ограничить** поля формы только теми, которые объявлены в Pydantic-модели. И **запретить** любые **дополнительные** поля.
/// note | Заметка
Этот функционал доступен с версии `0.114.0`. 🤓
Этот функционал доступен начиная с версии FastAPI `0.114.0`. 🤓
///
@@ -73,6 +73,6 @@ $ pip install python-multipart
}
```
## Заключение
## Итоги { #summary }
Вы можете использовать Pydantic-модели для объявления полей форм в FastAPI. 😎

View File

@@ -1,20 +1,24 @@
# Файлы и формы в запросе
# Файлы и формы в запросе { #request-forms-and-files }
Вы можете определять файлы и поля формы одновременно, используя `File` и `Form`.
/// info | Дополнительная информация
/// info | Информация
Чтобы получать загруженные файлы и/или данные форм, сначала установите <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>.
Например: `pip install python-multipart`.
Убедитесь, что вы создали [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активировали его, а затем установили пакет, например:
```console
$ pip install python-multipart
```
///
## Импортируйте `File` и `Form`
## Импортируйте `File` и `Form` { #import-file-and-form }
{* ../../docs_src/request_forms_and_files/tutorial001_an_py39.py hl[3] *}
## Определите параметры `File` и `Form`
## Определите параметры `File` и `Form` { #define-file-and-form-parameters }
Создайте параметры файла и формы таким же образом, как для `Body` или `Query`:
@@ -22,16 +26,16 @@
Файлы и поля формы будут загружены в виде данных формы, и вы получите файлы и поля формы.
Вы можете объявить некоторые файлы как `bytes`, а некоторые - как `UploadFile`.
Вы можете объявить некоторые файлы как `bytes`, а некоторые как `UploadFile`.
/// warning | Внимание
Вы можете объявить несколько параметров `File` и `Form` в операции *path*, но вы не можете также объявить поля `Body`, которые вы ожидаете получить в виде JSON, так как запрос будет иметь тело, закодированное с помощью `multipart/form-data` вместо `application/json`.
Вы можете объявить несколько параметров `File` и `Form` в операции пути, но вы не можете также объявить поля `Body`, которые вы ожидаете получить в виде JSON, так как запрос будет иметь тело, закодированное с помощью `multipart/form-data` вместо `application/json`.
Это не ограничение **Fast API**, это часть протокола HTTP.
Это не ограничение **FastAPI**, это часть протокола HTTP.
///
## Резюме
## Резюме { #recap }
Используйте `File` и `Form` вместе, когда необходимо получить данные и файлы в одном запросе.

View File

@@ -1,4 +1,4 @@
# Данные формы
# Данные формы { #form-data }
Когда вам нужно получить поля формы вместо JSON, вы можете использовать `Form`.
@@ -6,64 +6,68 @@
Чтобы использовать формы, сначала установите <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>.
Например, выполните команду `pip install python-multipart`.
Убедитесь, что вы создали [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активировали его, а затем установили пакет, например:
```console
$ pip install python-multipart
```
///
## Импорт `Form`
## Импорт `Form` { #import-form }
Импортируйте `Form` из `fastapi`:
{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[3] *}
## Определение параметров `Form`
## Определение параметров `Form` { #define-form-parameters }
Создайте параметры формы так же, как это делается для `Body` или `Query`:
{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[9] *}
Например, в одном из способов использования спецификации OAuth2 (называемом "потоком пароля") требуется отправить `username` и `password` в виде полей формы.
Например, в одном из способов использования спецификации OAuth2 (называемом «потоком пароля») требуется отправить `username` и `password` в виде полей формы.
Данный способ требует отправку данных для авторизации посредством формы (а не JSON) и обязательного наличия в форме строго именованных полей `username` и `password`.
<abbr title="specification спецификация">spec</abbr> требует, чтобы поля были строго названы `username` и `password` и отправлялись как поля формы, а не JSON.
Вы можете настроить `Form` точно так же, как настраиваете и `Body` ( `Query`, `Path`, `Cookie`), включая валидации, примеры, псевдонимы (например, `user-name` вместо `username`) и т.д.
С помощью `Form` вы можете объявить те же настройки, что и с `Body` (и `Query`, `Path`, `Cookie`), включая валидацию, примеры, псевдоним (например, `user-name` вместо `username`) и т.д.
/// info | Дополнительная информация
`Form` - это класс, который наследуется непосредственно от `Body`.
`Form` это класс, который наследуется непосредственно от `Body`.
///
/// tip | Подсказка
Вам необходимо явно указывать параметр `Form` при объявлении каждого поля, иначе поля будут интерпретироваться как параметры запроса или параметры тела (JSON).
Чтобы объявлять данные формы, вам нужно явно использовать `Form`, иначе параметры будут интерпретированы как параметры запроса или параметры тела (JSON).
///
## О "полях формы"
## О «полях формы» { #about-form-fields }
Обычно способ, которым HTML-формы (`<form></form>`) отправляют данные на сервер, использует "специальное" кодирование для этих данных, отличное от JSON.
Обычно способ, которым HTML-формы (`<form></form>`) отправляют данные на сервер, использует «специальное» кодирование для этих данных, отличное от JSON.
**FastAPI** гарантирует правильное чтение этих данных из соответствующего места, а не из JSON.
**FastAPI** гарантирует, что эти данные будут прочитаны из нужного места, а не из JSON.
/// note | Технические детали
Данные из форм обычно кодируются с использованием "типа медиа" `application/x-www-form-urlencoded`.
Данные из форм обычно кодируются с использованием «типа содержимого» `application/x-www-form-urlencoded`.
Но когда форма содержит файлы, она кодируется как `multipart/form-data`. Вы узнаете о работе с файлами в следующей главе.
Но когда форма содержит файлы, она кодируется как `multipart/form-data`. О работе с файлами вы прочтёте в следующей главе.
Если вы хотите узнать больше про кодировки и поля формы, ознакомьтесь с <a href="https://developer.mozilla.org/ru/docs/Web/HTTP/Methods/POST" class="external-link" target="_blank">документацией <abbr title="Mozilla Developer Network">MDN</abbr> для `POST` на веб-сайте</a>.
Если вы хотите узнать больше про эти кодировки и поля формы, обратитесь к <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" class="external-link" target="_blank"><abbr title="Mozilla Developer Network Сеть разработчиков Mozilla">MDN</abbr> веб-документации для `POST`</a>.
///
/// warning | Предупреждение
Вы можете объявлять несколько параметров `Form` в *операции пути*, но вы не можете одновременно с этим объявлять поля `Body`, которые вы ожидаете получить в виде JSON, так как запрос будет иметь тело, закодированное с использованием `application/x-www-form-urlencoded`, а не `application/json`.
Вы можете объявлять несколько параметров `Form` в *операции пути*, но вы не можете одновременно объявлять поля `Body`, которые вы ожидаете получить в виде JSON, так как запрос будет иметь тело, закодированное с использованием `application/x-www-form-urlencoded`, а не `application/json`.
Это не ограничение **FastAPI**, это часть протокола HTTP.
///
## Резюме
## Резюме { #recap }
Используйте `Form` для объявления входных параметров данных формы.

View File

@@ -1,237 +1,247 @@
# Модель ответа - Возвращаемый тип
# Модель ответа Возвращаемый тип { #response-model-return-type }
Вы можете объявить тип ответа, указав аннотацию **возвращаемого значения** для *функции операции пути*.
Вы можете объявить тип, используемый для ответа, указав аннотацию **возвращаемого значения** для *функции-обработчика пути*.
FastAPI позволяет использовать **аннотации типов** таким же способом, как и для ввода данных в **параметры** функции, вы можете использовать модели Pydantic, списки, словари, скалярные типы (такие, как int, bool и т.д.).
Вы можете использовать **аннотации типов** так же, как и для входных данных в **параметрах** функции: Pydantic-модели, списки, словари, скалярные значения (целые числа, булевы и т.д.).
{* ../../docs_src/response_model/tutorial001_01_py310.py hl[16,21] *}
FastAPI будет использовать этот возвращаемый тип для:
FastAPI будет использовать этот тип ответа для:
* **Валидации** ответа.
* Если данные невалидны (например, отсутствует одно из полей), это означает, что код *вашего* приложения работает некорректно и функция возвращает не то, что вы ожидаете. В таком случае приложение вернет server error вместо того, чтобы отправить неправильные данные. Таким образом, вы и ваши пользователи можете быть уверены, что получите корректные данные в том виде, в котором они ожидаются.
* Добавьте **JSON схему** для ответа внутри *операции пути* OpenAPI.
* Она будет использована для **автоматически генерируемой документации**.
* А также - для автоматической кодогенерации пользователями.
* **Валидации** возвращаемых данных.
* Если данные невалидны (например, отсутствует поле), это означает, что код *вашего* приложения работает некорректно и возвращает не то, что должен. В таком случае будет возвращена ошибка сервера вместо неправильных данных. Так вы и ваши клиенты можете быть уверены, что получите ожидаемые данные и ожидаемую структуру.
* Добавления **JSON Schema** для ответа в OpenAPI *операции пути*.
* Это будет использовано **автоматической документацией**.
* Это также будет использовано инструментами автоматической генерации клиентского кода.
Но самое важное:
Но самое главное:
* Ответ будет **ограничен и отфильтрован** - т.е. в нем останутся только те данные, которые определены в возвращаемом типе.
* Это особенно важно для **безопасности**, далее мы рассмотрим эту тему подробнее.
* Выходные данные будут **ограничены и отфильтрованы** в соответствии с тем, что определено в возвращаемом типе.
* Это особенно важно для **безопасности**, ниже мы рассмотрим это подробнее.
## Параметр `response_model`
## Параметр `response_model` { #response-model-parameter }
Бывают случаи, когда вам необходимо (или просто хочется) возвращать данные, которые не полностью соответствуют объявленному типу.
Бывают случаи, когда вам нужно или хочется возвращать данные, которые не в точности соответствуют объявленному типу.
Допустим, вы хотите, чтобы ваша функция **возвращала словарь (dict)** или объект из базы данных, но при этом **объявляете выходной тип как модель Pydantic**. Тогда именно указанная модель будет использована для автоматической документации, валидации и т.п. для объекта, который вы вернули (например, словаря или объекта из базы данных).
Например, вы можете хотеть **возвращать словарь (dict)** или объект из базы данных, но **объявить его как Pydantic-модель**. Тогда Pydantic-модель выполнит документирование данных, валидацию и т.п. для объекта, который вы вернули (например, словаря или объекта из базы данных).
Но если указать аннотацию возвращаемого типа, статическая проверка типов будет выдавать ошибку (абсолютно корректную в данном случае). Она будет говорить о том, что ваша функция должна возвращать данные одного типа (например, dict), а в аннотации вы объявили другой тип (например, модель Pydantic).
Если вы добавите аннотацию возвращаемого типа, инструменты и редакторы кода начнут жаловаться (и будут правы), что функция возвращает тип (например, dict), отличный от объявленного (например, Pydantic-модель).
В таком случае можно использовать параметр `response_model` внутри *декоратора операции пути* вместо аннотации возвращаемого значения функции.
В таких случаях вместо аннотации возвращаемого типа можно использовать параметр `response_model` у *декоратора операции пути*.
Параметр `response_model` может быть указан для любой *операции пути*:
Параметр `response_model` можно указать у любой *операции пути*:
* `@app.get()`
* `@app.post()`
* `@app.put()`
* `@app.delete()`
* и др.
* и т.д.
{* ../../docs_src/response_model/tutorial001_py310.py hl[17,22,24:27] *}
/// note | Технические детали
/// note | Примечание
Помните, что параметр `response_model` является параметром именно декоратора http-методов (`get`, `post`, и т.п.). Не следует его указывать для *функций операций пути*, как вы бы поступили с другими параметрами или с телом запроса.
Обратите внимание, что `response_model` — это параметр метода «декоратора» (`get`, `post` и т.д.), а не вашей *функции-обработчика пути*, в которой указываются параметры и тело запроса.
///
`response_model` принимает те же типы, которые можно указать для какого-либо поля в модели Pydantic. Таким образом, это может быть как одиночная модель Pydantic, так и `список (list)` моделей Pydantic. Например, `List[Item]`.
`response_model` принимает тот же тип, что вы бы объявили для поля Pydantic-модели, то есть это может быть одна Pydantic-модель, а может быть, например, `list` Pydantic-моделей, как `List[Item]`.
FastAPI будет использовать значение `response_model` для того, чтобы автоматически генерировать документацию, производить валидацию и т.п. А также для **конвертации и фильтрации выходных данных** в объявленный тип.
FastAPI будет использовать `response_model` для документации, валидации и т. п., а также для **конвертации и фильтрации выходных данных** к объявленному типу.
/// tip | Подсказка
/// tip | Совет
Если вы используете анализаторы типов со строгой проверкой (например, mypy), можно указать `Any` в качестве типа возвращаемого значения функции.
Если у вас в редакторе кода, mypy и т. п. включены строгие проверки типов, вы можете объявить возвращаемый тип функции как `Any`.
Таким образом вы информируете ваш редактор кода, что намеренно возвращаете данные неопределенного типа. Но возможности FastAPI, такие как автоматическая генерация документации, валидация, фильтрация и т.д. все так же будут работать, просто используя параметр `response_model`.
Так вы сообщите редактору, что намеренно возвращаете что угодно. Но FastAPI всё равно выполнит документацию данных, валидацию, фильтрацию и т.д. с помощью `response_model`.
///
### Приоритет `response_model`
### Приоритет `response_model` { #response-model-priority }
Если одновременно указать аннотацию типа для ответа функции и параметр `response_model` - последний будет иметь больший приоритет и FastAPI будет использовать именно его.
Если вы объявите и возвращаемый тип, и `response_model`, приоритет будет у `response_model`, именно его использует FastAPI.
Таким образом вы можете объявить корректные аннотации типов к вашим функциям, даже если они возвращают тип, отличающийся от указанного в `response_model`. Они будут считаны во время статической проверки типов вашими помощниками, например, mypy. При этом вы все так же используете возможности FastAPI для автоматической документации, валидации и т.д. благодаря `response_model`.
Так вы можете добавить корректные аннотации типов к своим функциям, даже если фактически возвращаете тип, отличный от модели ответа, чтобы ими пользовались редактор и инструменты вроде mypy. И при этом FastAPI продолжит выполнять валидацию данных, документацию и т.д. с использованием `response_model`.
Вы можете указать значение `response_model=None`, чтобы отключить создание модели ответа для данной *операции пути*. Это может понадобиться, если вы добавляете аннотации типов для данных, не являющихся валидными полями Pydantic. Мы увидим пример кода для такого случая в одном из разделов ниже.
Вы также можете указать `response_model=None`, чтобы отключить создание модели ответа для данной *операции пути*. Это может понадобиться, если вы добавляете аннотации типов для вещей, не являющихся валидными полями Pydantic. Пример вы увидите ниже.
## Получить и вернуть один и тот же тип данных
## Вернуть те же входные данные { #return-the-same-input-data }
Здесь мы объявили модель `UserIn`, которая хранит пользовательский пароль в открытом виде:
Здесь мы объявляем модель `UserIn`, она будет содержать пароль в открытом виде:
{* ../../docs_src/response_model/tutorial002_py310.py hl[7,9] *}
/// info | Информация
Чтобы использовать `EmailStr`, прежде необходимо установить <a href="https://github.com/JoshData/python-email-validator" class="external-link" target="_blank">`email-validator`</a>.
Используйте `pip install email-validator`
или `pip install pydantic[email]`.
Чтобы использовать `EmailStr`, сначала установите <a href="https://github.com/JoshData/python-email-validator" class="external-link" target="_blank">`email-validator`</a>.
Создайте [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активируйте его и затем установите пакет, например:
```console
$ pip install email-validator
```
или так:
```console
$ pip install "pydantic[email]"
```
///
Далее мы используем нашу модель в аннотациях типа как для аргумента функции, так и для выходного значения:
И мы используем эту модель для объявления входных данных, и ту же модель — для объявления выходных данных:
{* ../../docs_src/response_model/tutorial002_py310.py hl[16] *}
Теперь всякий раз, когда клиент создает пользователя с паролем, API будет возвращать его пароль в ответе.
Теперь, когда браузер создаёт пользователя с паролем, API вернёт тот же пароль в ответе.
В данном случае это не такая уж большая проблема, поскольку ответ получит тот же самый пользователь, который и создал пароль.
В этом случае это может быть не проблемой, так как пароль отправляет тот же пользователь.
Но что если мы захотим использовать эту модель для какой-либо другой *операции пути*? Мы можем, сами того не желая, отправить пароль любому другому пользователю.
Но если мы используем ту же модель в другой *операции пути*, мы можем начать отправлять пароли пользователей каждому клиенту.
/// danger | Осторожно
Никогда не храните пароли пользователей в открытом виде, а также никогда не возвращайте их в ответе, как в примере выше. В противном случае - убедитесь, что вы хорошо продумали и учли все возможные риски такого подхода и вам известно, что вы делаете.
Никогда не храните пароль пользователя в открытом виде и не отправляйте его в ответе подобным образом, если только вы не понимаете всех рисков и точно знаете, что делаете.
///
## Создание модели для ответа
## Добавить модель для ответа { #add-an-output-model }
Вместо этого мы можем создать входную модель, хранящую пароль в открытом виде и выходную модель без пароля:
Вместо этого мы можем создать входную модель с паролем в открытом виде и выходную модель без него:
{* ../../docs_src/response_model/tutorial003_py310.py hl[9,11,16] *}
В таком случае, даже несмотря на то, что наша *функция операции пути* возвращает тот же самый объект пользователя с паролем, полученным на вход:
Здесь, хотя *функция-обработчик пути* возвращает тот же входной объект пользователя, содержащий пароль:
{* ../../docs_src/response_model/tutorial003_py310.py hl[24] *}
...мы указали в `response_model` модель `UserOut`, в которой отсутствует поле, содержащее пароль - и он будет исключен из ответа:
...мы объявили `response_model` как модель `UserOut`, в которой нет пароля:
{* ../../docs_src/response_model/tutorial003_py310.py hl[22] *}
Таким образом **FastAPI** позаботится о фильтрации ответа и исключит из него всё, что не указано в выходной модели (при помощи Pydantic).
Таким образом, **FastAPI** позаботится о том, чтобы отфильтровать все данные, не объявленные в выходной модели (используя Pydantic).
### `response_model` или возвращаемый тип данных
### `response_model` или возвращаемый тип { #response-model-or-return-type }
В нашем примере модели входных данных и выходных данных различаются. И если мы укажем аннотацию типа выходного значения функции как `UserOut` - проверка типов выдаст ошибку из-за того, что мы возвращаем некорректный тип. Поскольку это 2 разных класса.
В этом случае, поскольку две модели различаются, если бы мы аннотировали возвращаемый тип функции как `UserOut`, редактор и инструменты пожаловались бы, что мы возвращаем неверный тип, так как это разные классы.
Поэтому в нашем примере мы можем объявить тип ответа только в параметре `response_model`.
Поэтому в этом примере мы должны объявить тип ответа в параметре `response_model`.
...но продолжайте читать дальше, чтобы узнать как можно это обойти.
...но читайте дальше, чтобы узнать, как это обойти.
## Возвращаемый тип и Фильтрация данных
## Возвращаемый тип и фильтрация данных { #return-type-and-data-filtering }
Продолжим рассматривать предыдущий пример. Мы хотели **аннотировать входные данные одним типом**, а выходное значение - **другим типом**.
Продолжим предыдущий пример. Мы хотели **аннотировать функцию одним типом**, но при этом иметь возможность вернуть из функции что-то, что фактически включает **больше данных**.
Мы хотим, чтобы FastAPI продолжал **фильтровать** данные, используя `response_model`.
Мы хотим, чтобы FastAPI продолжал **фильтровать** данные с помощью модели ответа. Так что, даже если функция возвращает больше данных, в ответ будут включены только поля, объявленные в модели ответа.
В прошлом примере, т.к. входной и выходной типы являлись разными классами, мы были вынуждены использовать параметр `response_model`. И как следствие, мы лишались помощи статических анализаторов для проверки ответа функции.
В предыдущем примере, поскольку классы были разными, нам пришлось использовать параметр `response_model`. Но это также означает, что мы теряем поддержку от редактора и инструментов, проверяющих возвращаемый тип функции.
Но в подавляющем большинстве случаев мы будем хотеть, чтобы модель ответа лишь **фильтровала/удаляла** некоторые данные из ответа, как в нашем примере.
Однако в большинстве таких случаев нам нужно лишь **отфильтровать/убрать** некоторые данные, как в этом примере.
И в таких случаях мы можем использовать классы и наследование, чтобы пользоваться преимуществами **аннотаций типов** и получать более полную статическую проверку типов. Но при этом все так же получать **фильтрацию ответа** от FastAPI.
И в этих случаях мы можем использовать классы и наследование, чтобы воспользоваться **аннотациями типов** функций для лучшей поддержки в редакторе и инструментах и при этом получить **фильтрацию данных** от FastAPI.
{* ../../docs_src/response_model/tutorial003_01_py310.py hl[7:10,13:14,18] *}
Таким образом, мы получаем поддержку редактора кода и mypy в части типов, сохраняя при этом фильтрацию данных от FastAPI.
Так мы получаем поддержку инструментов (редакторы, mypy) — код корректен с точки зрения типов — и одновременно получаем фильтрацию данных от FastAPI.
Как это возможно? Давайте разберемся. 🤓
Как это работает? Давайте разберёмся. 🤓
### Аннотации типов и инструменты для их проверки
### Аннотации типов и инструменты { #type-annotations-and-tooling }
Для начала давайте рассмотрим как наш редактор кода, mypy и другие помощники разработчика видят аннотации типов.
Сначала посмотрим, как это увидят редакторы, mypy и другие инструменты.
У модели `BaseUser` есть некоторые поля. Затем `UserIn` наследуется от `BaseUser` и добавляет новое поле `password`. Таким образом модель будет включать в себя все поля из первой модели (родителя), а также свои собственные.
`BaseUser` содержит базовые поля. Затем `UserIn` наследуется от `BaseUser` и добавляет поле `password`, то есть он включает все поля обеих моделей.
Мы аннотируем возвращаемый тип функции как `BaseUser`, но фактически мы будем возвращать объект типа `UserIn`.
Мы аннотируем возвращаемый тип функции как `BaseUser`, но фактически возвращаем экземпляр `UserIn`.
Редакторы, mypy и другие инструменты не будут иметь возражений против такого подхода, поскольку `UserIn` является подклассом `BaseUser`. Это означает, что такой тип будет *корректным*, т.к. ответ может быть чем угодно, если это будет `BaseUser`.
Редактор, mypy и другие инструменты не будут возражать, потому что с точки зрения типов `UserIn` подкласс `BaseUser`, что означает, что это *валидный* тип везде, где ожидается что-то, являющееся `BaseUser`.
### Фильтрация Данных FastAPI
### Фильтрация данных FastAPI { #fastapi-data-filtering }
FastAPI знает тип ответа функции, так что вы можете быть уверены, что на выходе будут **только** те поля, которые вы указали.
Теперь, для FastAPI: он увидит возвращаемый тип и убедится, что то, что вы возвращаете, включает **только** поля, объявленные в этом типе.
FastAPI совместно с Pydantic выполнит некоторую магию "под капотом", чтобы убедиться, что те же самые правила наследования классов не используются для фильтрации возвращаемых данных, в противном случае вы могли бы в конечном итоге вернуть гораздо больше данных, чем ожидали.
FastAPI делает несколько вещей внутри вместе с Pydantic, чтобы гарантировать, что те же правила наследования классов не используются для фильтрации возвращаемых данных, иначе вы могли бы вернуть гораздо больше данных, чем ожидали.
Таким образом, вы можете получить все самое лучшее из обоих миров: аннотации типов с **поддержкой инструментов для разработки** и **фильтрацию данных**.
Таким образом вы получаете лучшее из обоих миров: аннотации типов с **поддержкой инструментов** и **фильтрацию данных**.
## Автоматическая документация
## Посмотреть в документации { #see-it-in-the-docs }
Если посмотреть на сгенерированную документацию, вы можете убедиться, что в ней присутствуют обе JSON схемы - как для входной модели, так и для выходной:
В автоматической документации вы увидите, что у входной и выходной моделей есть свои JSON Schema:
<img src="/img/tutorial/response-model/image01.png">
И также обе модели будут использованы в интерактивной документации API:
И обе модели используются в интерактивной документации API:
<img src="/img/tutorial/response-model/image02.png">
## Другие аннотации типов
## Другие аннотации возвращаемых типов { #other-return-type-annotations }
Бывают случаи, когда вы возвращаете что-то, что не является валидным типом для Pydantic и вы указываете аннотацию ответа функции только для того, чтобы работала поддержка различных инструментов (редактор кода, mypy и др.).
Бывают случаи, когда вы возвращаете что-то, что не является валидным полем Pydantic, и аннотируете это в функции только ради поддержки инструментов (редактор, mypy и т. д.).
### Возвращаем Response
### Возврат Response напрямую { #return-a-response-directly }
Самый частый сценарий использования - это [возвращать Response напрямую, как описано в расширенной документации](../advanced/response-directly.md){.internal-link target=_blank}.
Самый распространённый случай — [возвращать Response напрямую, как описано далее в разделах для продвинутых](../advanced/response-directly.md){.internal-link target=_blank}.
{* ../../docs_src/response_model/tutorial003_02.py hl[8,10:11] *}
Это поддерживается FastAPI по-умолчанию, т.к. аннотация проставлена в классе (или подклассе) `Response`.
Этот простой случай обрабатывается FastAPI автоматически, потому что аннотация возвращаемого типа — это класс (или подкласс) `Response`.
И ваши помощники разработки также будут счастливы, т.к. оба класса `RedirectResponse` и `JSONResponse` являются подклассами `Response`. Таким образом мы получаем корректную аннотацию типа.
И инструменты тоже будут довольны, потому что и `RedirectResponse`, и `JSONResponse` являются подклассами `Response`, так что аннотация типа корректна.
### Подкласс Response в аннотации типа
### Аннотировать подкласс Response { #annotate-a-response-subclass }
Вы также можете указать подкласс `Response` в аннотации типа:
Вы также можете использовать подкласс `Response` в аннотации типа:
{* ../../docs_src/response_model/tutorial003_03.py hl[8:9] *}
Это сработает, потому что `RedirectResponse` является подклассом `Response` и FastAPI автоматически обработает этот простейший случай.
Это тоже сработает, так как `RedirectResponse` подкласс `Response`, и FastAPI автоматически обработает этот случай.
### Некорректные аннотации типов
### Некорректные аннотации возвращаемых типов { #invalid-return-type-annotations }
Но когда вы возвращаете какой-либо другой произвольный объект, который не является допустимым типом Pydantic (например, объект из базы данных), и вы аннотируете его подобным образом для функции, FastAPI попытается создать из этого типа модель Pydantic и потерпит неудачу.
Но когда вы возвращаете произвольный объект, не являющийся валидным типом Pydantic (например, объект базы данных), и аннотируете его таким образом в функции, FastAPI попытается создать модель ответа Pydantic из этой аннотации типа и потерпит неудачу.
То же самое произошло бы, если бы у вас было что-то вроде <abbr title='Union разных типов буквально означает "любой из перечисленных типов".'>Union</abbr> различных типов и один или несколько из них не являлись бы допустимыми типами для Pydantic. Например, такой вариант приведет к ошибке 💥:
То же произойдёт, если у вас будет что-то вроде <abbr title='Объединение нескольких типов означает «любой из этих типов».'>union</abbr> разных типов, где один или несколько не являются валидными типами Pydantic, например, это приведёт к ошибке 💥:
{* ../../docs_src/response_model/tutorial003_04_py310.py hl[8] *}
...такой код вызовет ошибку, потому что в аннотации указан неподдерживаемый Pydantic тип. А также этот тип не является классом или подклассом `Response`.
...это не сработает, потому что аннотация типа не является типом Pydantic и это не единственный класс `Response` или его подкласс, а объединение (`union`) из `Response` и `dict`.
### Возможно ли отключить генерацию модели ответа?
### Отключить модель ответа { #disable-response-model }
Продолжим рассматривать предыдущий пример. Допустим, что вы хотите отказаться от автоматической валидации ответа, документации, фильтрации и т.д.
Продолжая пример выше, вы можете не хотеть использовать стандартную валидацию данных, документацию, фильтрацию и т.д., выполняемые FastAPI.
Но в то же время, хотите сохранить аннотацию возвращаемого типа для функции, чтобы обеспечить работу помощников и анализаторов типов (например, mypy).
Но при этом вы можете хотеть сохранить аннотацию возвращаемого типа в функции, чтобы пользоваться поддержкой инструментов (редакторы, проверки типов вроде mypy).
В таком случае, вы можете отключить генерацию модели ответа, указав `response_model=None`:
В этом случае вы можете отключить генерацию модели ответа, установив `response_model=None`:
{* ../../docs_src/response_model/tutorial003_05_py310.py hl[7] *}
Тогда FastAPI не станет генерировать модель ответа и вы сможете сохранить такую аннотацию типа, которая вам требуется, никак не влияя на работу FastAPI. 🤓
Так FastAPI пропустит генерацию модели ответа, и вы сможете использовать любые аннотации возвращаемых типов, не влияя на ваше приложение FastAPI. 🤓
## Параметры модели ответа
## Параметры кодирования модели ответа { #response-model-encoding-parameters }
Модель ответа может иметь значения по умолчанию, например:
У вашей модели ответа могут быть значения по умолчанию, например:
{* ../../docs_src/response_model/tutorial004_py310.py hl[9,11:12] *}
* `description: Union[str, None] = None` (или `str | None = None` в Python 3.10), где `None` является значением по умолчанию.
* `tax: float = 10.5`, где `10.5` является значением по умолчанию.
* `tags: List[str] = []`, где пустой список `[]` является значением по умолчанию.
* `description: Union[str, None] = None` (или `str | None = None` в Python 3.10) имеет значение по умолчанию `None`.
* `tax: float = 10.5` имеет значение по умолчанию `10.5`.
* `tags: List[str] = []` имеет значение по умолчанию пустого списка: `[]`.
но вы, возможно, хотели бы исключить их из ответа, если данные поля не были заданы явно.
но вы можете захотеть опустить их в результате, если они фактически не были сохранены.
Например, у вас есть модель с множеством необязательных полей в NoSQL базе данных, но вы не хотите отправлять в качестве ответа очень длинный JSON с множеством значений по умолчанию.
Например, если у вас есть модели с множеством необязательных атрибутов в NoSQL-базе данных, но вы не хотите отправлять очень длинные JSON-ответы, заполненные значениями по умолчанию.
### Используйте параметр `response_model_exclude_unset`
### Используйте параметр `response_model_exclude_unset` { #use-the-response-model-exclude-unset-parameter }
Установите для *декоратора операции пути* параметр `response_model_exclude_unset=True`:
Вы можете установить у *декоратора операции пути* параметр `response_model_exclude_unset=True`:
{* ../../docs_src/response_model/tutorial004_py310.py hl[22] *}
и тогда значения по умолчанию не будут включены в ответ. В нем будут только те поля, значения которых фактически были установлены.
и эти значения по умолчанию не будут включены в ответ только те значения, которые действительно были установлены.
Итак, если вы отправите запрос на данную *операцию пути* для элемента, с ID = `Foo` - ответ (с исключенными значениями по-умолчанию) будет таким:
Итак, если вы отправите запрос к этой *операции пути* для элемента с ID `foo`, ответ (без значений по умолчанию) будет таким:
```JSON
{
@@ -242,7 +252,15 @@ FastAPI совместно с Pydantic выполнит некоторую ма
/// info | Информация
"Под капотом" FastAPI использует метод `.dict()` у объектов моделей Pydantic <a href="https://docs.pydantic.dev/latest/concepts/serialization/#modeldict" class="external-link" target="_blank">с параметром `exclude_unset`</a>, чтобы достичь такого эффекта.
В Pydantic v1 метод назывался `.dict()`, в Pydantic v2 он был помечен как устаревший (но всё ещё поддерживается) и переименован в `.model_dump()`.
Примеры здесь используют `.dict()` для совместимости с Pydantic v1, но если вы используете Pydantic v2, применяйте `.model_dump()`.
///
/// info | Информация
FastAPI использует метод `.dict()` у Pydantic-моделей с <a href="https://docs.pydantic.dev/1.10/usage/exporting_models/#modeldict" class="external-link" target="_blank">параметром `exclude_unset`</a>, чтобы добиться такого поведения.
///
@@ -253,13 +271,13 @@ FastAPI совместно с Pydantic выполнит некоторую ма
* `response_model_exclude_defaults=True`
* `response_model_exclude_none=True`
как описано в <a href="https://docs.pydantic.dev/latest/concepts/serialization/#modeldict" class="external-link" target="_blank">документации Pydantic</a> для параметров `exclude_defaults` и `exclude_none`.
как описано в <a href="https://docs.pydantic.dev/1.10/usage/exporting_models/#modeldict" class="external-link" target="_blank">документации Pydantic</a> для `exclude_defaults` и `exclude_none`.
///
#### Если значение поля отличается от значения по-умолчанию
#### Данные со значениями для полей, имеющих значения по умолчанию { #data-with-values-for-fields-with-defaults }
Если для некоторых полей модели, имеющих значения по-умолчанию, значения были явно установлены - как для элемента с ID = `Bar`, ответ будет таким:
Но если в ваших данных есть значения для полей модели, для которых указаны значения по умолчанию, как у элемента с ID `bar`:
```Python hl_lines="3 5"
{
@@ -270,11 +288,11 @@ FastAPI совместно с Pydantic выполнит некоторую ма
}
```
они не будут исключены из ответа.
они будут включены в ответ.
#### Если значение поля совпадает с его значением по умолчанию
#### Данные с такими же значениями, как значения по умолчанию { #data-with-the-same-values-as-the-defaults }
Если данные содержат те же значения, которые являются для этих полей по умолчанию, но были установлены явно - как для элемента с ID = `baz`, ответ будет таким:
Если данные имеют те же значения, что и значения по умолчанию, как у элемента с ID `baz`:
```Python hl_lines="3 5-6"
{
@@ -286,54 +304,54 @@ FastAPI совместно с Pydantic выполнит некоторую ма
}
```
FastAPI достаточно умен (на самом деле, это заслуга Pydantic), чтобы понять, что, хотя `description`, `tax` и `tags` хранят такие же данные, какие должны быть по умолчанию - для них эти значения были установлены явно (а не получены из значений по умолчанию).
FastAPI достаточно умен (на самом деле, это Pydantic), чтобы понять, что хотя `description`, `tax` и `tags` совпадают со значениями по умолчанию, они были установлены явно (а не взяты из значений по умолчанию).
И поэтому, они также будут включены в JSON ответа.
Поэтому они тоже будут включены в JSON-ответ.
/// tip | Подсказка
/// tip | Совет
Значением по умолчанию может быть что угодно, не только `None`.
Обратите внимание, что значения по умолчанию могут быть любыми, не только `None`.
Им может быть и список (`[]`), значение 10.5 типа `float`, и т.п.
Это может быть список (`[]`), число с плавающей точкой `10.5` и т. д.
///
### `response_model_include` и `response_model_exclude`
### `response_model_include` и `response_model_exclude` { #response-model-include-and-response-model-exclude }
Вы также можете использовать параметры *декоратора операции пути*, такие, как `response_model_include` и `response_model_exclude`.
Вы также можете использовать параметры *декоратора операции пути* `response_model_include` и `response_model_exclude`.
Они принимают аргументы типа `set`, состоящий из строк (`str`) с названиями атрибутов, которые либо требуется включить в ответ (при этом исключив все остальные), либо наоборот исключить (оставив в ответе все остальные поля).
Они принимают `set` из `str` с именами атрибутов, которые нужно включить (исключив остальные) или исключить (оставив остальные).
Это можно использовать как быстрый способ исключить данные из ответа, не создавая отдельную модель Pydantic.
Это можно использовать как быстрый способ, если у вас только одна Pydantic-модель и вы хотите убрать часть данных из ответа.
/// tip | Подсказка
/// tip | Совет
Но по-прежнему рекомендуется следовать изложенным выше советам и использовать несколько моделей вместо данных параметров.
Но всё же рекомендуется использовать подходы выше — несколько классов — вместо этих параметров.
Потому как JSON схема OpenAPI, генерируемая вашим приложением (а также документация) все еще будет содержать все поля, даже если вы использовали `response_model_include` или `response_model_exclude` и исключили некоторые атрибуты.
Потому что JSON Schema, генерируемая в OpenAPI вашего приложения (и документации), всё равно будет соответствовать полной модели, даже если вы используете `response_model_include` или `response_model_exclude`, чтобы опустить некоторые атрибуты.
То же самое применимо к параметру `response_model_by_alias`.
То же относится к `response_model_by_alias`, который работает аналогично.
///
{* ../../docs_src/response_model/tutorial005_py310.py hl[29,35] *}
/// tip | Подсказка
/// tip | Совет
При помощи кода `{"name","description"}` создается объект множества (`set`) с двумя строковыми значениями.
Синтаксис `{"name", "description"}` создаёт `set` с этими двумя значениями.
Того же самого можно достичь используя `set(["name", "description"])`.
Это эквивалентно `set(["name", "description"])`.
///
#### Что если использовать `list` вместо `set`?
#### Использование `list` вместо `set` { #using-lists-instead-of-sets }
Если вы забыли про `set` и использовали структуру `list` или `tuple`, FastAPI автоматически преобразует этот объект в `set`, чтобы обеспечить корректную работу:
Если вы забыли использовать `set` и применили `list` или `tuple`, FastAPI всё равно преобразует это в `set`, и всё будет работать корректно:
{* ../../docs_src/response_model/tutorial006_py310.py hl[29,35] *}
## Резюме
## Резюме { #recap }
Используйте параметр `response_model` у *декоратора операции пути* для того, чтобы задать модель ответа и в большей степени для того, чтобы быть уверенным, что приватная информация будет отфильтрована.
Используйте параметр `response_model` у *декоратора операции пути*, чтобы задавать модели ответа, и особенно — чтобы приватные данные отфильтровывались.
А также используйте `response_model_exclude_unset`, чтобы возвращать только те значения, которые были заданы явно.
Используйте `response_model_exclude_unset`, чтобы возвращать только те значения, которые были установлены явно.

View File

@@ -1,6 +1,6 @@
# HTTP коды статуса ответа
# Статус-код ответа { #response-status-code }
Вы можете задать HTTP код статуса ответа с помощью параметра `status_code` подобно тому, как вы определяете схему ответа в любой из *операций пути*:
Подобно тому, как вы можете задать модель/схему ответа, вы можете объявить HTTP статус-код, используемый для ответа, с помощью параметра `status_code` в любой из *операций пути*:
* `@app.get()`
* `@app.post()`
@@ -12,11 +12,11 @@
/// note | Примечание
Обратите внимание, что `status_code` является атрибутом метода-декоратора (`get`, `post` и т.д.), а не *функции-обработчика пути* в отличие от всех остальных параметров и тела запроса.
Обратите внимание, что `status_code` — это параметр метода-декоратора (`get`, `post` и т.д.), а не вашей *функции-обработчика пути*, в отличие от всех остальных параметров и тела запроса.
///
Параметр `status_code` принимает число, обозначающее HTTP код статуса ответа.
Параметр `status_code` принимает число, обозначающее HTTP статус-код.
/// info | Информация
@@ -27,7 +27,7 @@
Это позволит:
* Возвращать указанный код статуса в ответе.
* Документировать его как код статуса ответа в OpenAPI схеме (а значит, и в пользовательском интерфейсе):
* Документировать его как код статуса ответа в OpenAPI схеме (а значит, и в пользовательских интерфейсах):
<img src="/img/tutorial/response-status-code/image01.png">
@@ -39,11 +39,11 @@ FastAPI знает об этом и создаст документацию Open
///
## Об HTTP кодах статуса ответа
## Об HTTP статус-кодах { #about-http-status-codes }
/// note | Примечание
Если вы уже знаете, что представляют собой HTTP коды статуса ответа, можете перейти к следующему разделу.
Если вы уже знаете, что представляют собой HTTP статус-коды, можете перейти к следующему разделу.
///
@@ -51,26 +51,26 @@ FastAPI знает об этом и создаст документацию Open
У кодов статуса есть названия, чтобы упростить их распознавание, но важны именно числовые значения.
Кратко о значениях кодов:
Кратко:
* `1XX` статус-коды информационного типа. Они редко используются разработчиками напрямую. Ответы с этими кодами не могут иметь тела.
* **`2XX`** статус-коды, сообщающие об успешной обработке запроса. Они используются чаще всего.
* `100 - 199` статус-коды информационного типа. Они редко используются разработчиками напрямую. Ответы с этими кодами не могут иметь тела.
* **`200 - 299`** статус-коды, сообщающие об успешной обработке запроса. Они используются чаще всего.
* `200` это код статуса ответа по умолчанию, который означает, что все прошло "OK".
* Другим примером может быть статус `201`, "Created". Он обычно используется после создания новой записи в базе данных.
* Особый случай `204`, "No Content". Этот статус ответа используется, когда нет содержимого для возврата клиенту, и поэтому ответ не должен иметь тела.
* **`3XX`** статус-коды, сообщающие о перенаправлениях. Ответы с этими кодами статуса могут иметь или не иметь тело, за исключением ответов со статусом `304`, "Not Modified", у которых не должно быть тела.
* **`4XX`** статус-коды, сообщающие о клиентской ошибке. Это ещё одна наиболее часто используемая категория.
* Особый случай `204`, "No Content". Этот статус ответа используется, когда нет содержимого для возврата клиенту, и поэтому ответ не должен иметь тела.
* **`300 - 399`** статус-коды, сообщающие о перенаправлениях. Ответы с этими кодами статуса могут иметь или не иметь тело, за исключением ответов со статусом `304`, "Not Modified", у которых не должно быть тела.
* **`400 - 499`** статус-коды, сообщающие о клиентской ошибке. Это ещё одна наиболее часто используемая категория.
* Пример код `404` для статуса "Not Found".
* Для общих ошибок со стороны клиента можно просто использовать код `400`.
* `5XX`  статус-коды, сообщающие о серверной ошибке. Они почти никогда не используются разработчиками напрямую. Когда что-то идет не так в какой-то части кода вашего приложения или на сервере, он автоматически вернёт один из 5XX кодов.
* `500 - 599` статус-коды, сообщающие о серверной ошибке. Они почти никогда не используются разработчиками напрямую. Когда что-то идет не так в какой-то части кода вашего приложения или на сервере, он автоматически вернёт один из этих кодов статуса.
/// tip | Подсказка
Чтобы узнать больше о HTTP кодах статуса и о том, для чего каждый из них предназначен, ознакомьтесь с <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status" class="external-link" target="_blank">документацией <abbr title="Mozilla Developer Network">MDN</abbr> об HTTP кодах статуса ответа</a>.
Чтобы узнать больше о HTTP кодах статуса и о том, для чего каждый из них предназначен, ознакомьтесь с <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status" class="external-link" target="_blank"><abbr title="Mozilla Developer Network Сеть разработчиков Mozilla">MDN</abbr> документацией об HTTP статус-кодах</a>.
///
## Краткие обозначения для запоминания названий кодов
## Краткие обозначения для запоминания названий кодов { #shortcut-to-remember-the-names }
Рассмотрим предыдущий пример еще раз:
@@ -84,7 +84,7 @@ FastAPI знает об этом и создаст документацию Open
{* ../../docs_src/response_status_code/tutorial002.py hl[1,6] *}
Они содержат те же числовые значения, но позволяют использовать подсказки редактора для выбора кода статуса:
Они содержат те же числовые значения, но позволяют использовать автозавершение редактора кода для выбора кода статуса:
<img src="/img/tutorial/response-status-code/image02.png">
@@ -96,6 +96,6 @@ FastAPI знает об этом и создаст документацию Open
///
## Изменение кода статуса по умолчанию
## Изменение кода статуса по умолчанию { #changing-the-default }
Позже, в [Руководстве для продвинутых пользователей](../advanced/response-change-status-code.md){.internal-link target=_blank}, вы узнаете, как возвращать HTTP коды статуса, отличные от используемого здесь кода статуса по умолчанию.
Позже, в [Руководстве для продвинутых пользователей](../advanced/response-change-status-code.md){.internal-link target=_blank}, вы узнаете, как возвращать HTTP статус-код, отличный от значения по умолчанию, которое вы объявляете здесь.

View File

@@ -1,42 +1,70 @@
# Объявление примера запроса данных
# Объявление примеров данных запроса { #declare-request-example-data }
Вы можете объявлять примеры данных, которые ваше приложение может получать.
Вот несколько способов, как это можно сделать.
Вот несколько способов, как это сделать.
## Pydantic `schema_extra`
## Дополнительные данные JSON Schema в моделях Pydantic { #extra-json-schema-data-in-pydantic-models }
Вы можете объявить ключ `example` для модели Pydantic, используя класс `Config` и переменную `schema_extra`, как описано в <a href="https://docs.pydantic.dev/latest/concepts/json_schema/#schema-customization" class="external-link" target="_blank">Pydantic документации: Настройка схемы</a>:
Вы можете объявить `examples` для модели Pydantic, которые будут добавлены в сгенерированную JSON Schema.
{* ../../docs_src/schema_extra_example/tutorial001_py310.py hl[13:21] *}
//// tab | Pydantic v2
Эта дополнительная информация будет включена в **JSON Schema** выходных данных для этой модели, и она будет использоваться в документации к API.
{* ../../docs_src/schema_extra_example/tutorial001_py310.py hl[13:24] *}
////
//// tab | Pydantic v1
{* ../../docs_src/schema_extra_example/tutorial001_pv1_py310.py hl[13:23] *}
////
Эта дополнительная информация будет добавлена как есть в выходную **JSON Schema** этой модели и будет использоваться в документации API.
//// tab | Pydantic v2
В Pydantic версии 2 вы будете использовать атрибут `model_config`, который принимает `dict`, как описано в <a href="https://docs.pydantic.dev/latest/api/config/" class="external-link" target="_blank">Документации Pydantic: Конфигурация</a>.
Вы можете задать `"json_schema_extra"` с `dict`, содержащим любые дополнительные данные, которые вы хотите видеть в сгенерированной JSON Schema, включая `examples`.
////
//// tab | Pydantic v1
В Pydantic версии 1 вы будете использовать внутренний класс `Config` и `schema_extra`, как описано в <a href="https://docs.pydantic.dev/1.10/usage/schema/#schema-customization" class="external-link" target="_blank">Документации Pydantic: Настройка схемы</a>.
Вы можете задать `schema_extra` со `dict`, содержащим любые дополнительные данные, которые вы хотите видеть в сгенерированной JSON Schema, включая `examples`.
////
/// tip | Подсказка
Вы можете использовать тот же метод для расширения JSON-схемы и добавления своей собственной дополнительной информации.
Вы можете использовать тот же приём, чтобы расширить JSON Schema и добавить свою собственную дополнительную информацию.
Например, вы можете использовать это для добавления дополнительной информации для пользовательского интерфейса в вашем веб-приложении и т.д.
Например, вы можете использовать это, чтобы добавить метаданные для фронтенд‑пользовательского интерфейса и т.д.
///
## Дополнительные аргументы поля `Field`
/// info | Информация
При использовании `Field()` с моделями Pydantic, вы также можете объявлять дополнительную информацию для **JSON Schema**, передавая любые другие произвольные аргументы в функцию.
OpenAPI 3.1.0 (используется начиная с FastAPI 0.99.0) добавил поддержку `examples`, который является частью стандарта **JSON Schema**.
Вы можете использовать это, чтобы добавить аргумент `example` для каждого поля:
До этого поддерживалось только ключевое слово `example` с одним примером. Оно всё ещё поддерживается в OpenAPI 3.1.0, но помечено как устаревшее и не является частью стандарта JSON Schema. Поэтому рекомендуется мигрировать `example` на `examples`. 🤓
Подробнее — в конце этой страницы.
///
## Дополнительные аргументы `Field` { #field-additional-arguments }
При использовании `Field()` с моделями Pydantic вы также можете объявлять дополнительные `examples`:
{* ../../docs_src/schema_extra_example/tutorial002_py310.py hl[2,8:11] *}
/// warning | Внимание
## `examples` в JSON Schema — OpenAPI { #examples-in-json-schema-openapi }
Имейте в виду, что эти дополнительные переданные аргументы не добавляют никакой валидации, только дополнительную информацию для документации.
///
## Использование `example` и `examples` в OpenAPI
При использовании любой из этих функций:
При использовании любой из функций:
* `Path()`
* `Query()`
@@ -46,65 +74,151 @@
* `Form()`
* `File()`
вы также можете добавить аргумент, содержащий `example` или группу `examples` с дополнительной информацией, которая будет добавлена в **OpenAPI**.
вы также можете объявить набор `examples` с дополнительной информацией, которая будет добавлена в их **JSON Schema** внутри **OpenAPI**.
### Параметр `Body` с аргументом `example`
### `Body` с `examples` { #body-with-examples }
Здесь мы передаём аргумент `example`, как пример данных ожидаемых в параметре `Body()`:
Здесь мы передаём `examples`, содержащий один пример данных, ожидаемых в `Body()`:
{* ../../docs_src/schema_extra_example/tutorial003_an_py310.py hl[22:27] *}
{* ../../docs_src/schema_extra_example/tutorial003_an_py310.py hl[22:29] *}
### Аргумент "example" в UI документации
### Пример в UI документации { #example-in-the-docs-ui }
С любым из вышеуказанных методов это будет выглядеть так в `/docs`:
С любым из перечисленных выше методов это будет выглядеть так в `/docs`:
<img src="/img/tutorial/body-fields/image01.png">
### `Body` с аргументом `examples`
### `Body` с несколькими `examples` { #body-with-multiple-examples }
В качестве альтернативы одному аргументу `example`, вы можете передавать `examples` используя тип данных `dict` с **несколькими примерами**, каждый из которых содержит дополнительную информацию, которая также будет добавлена в **OpenAPI**.
Конечно, вы можете передать и несколько `examples`:
Ключи `dict` указывают на каждый пример, а значения для каждого из них - на еще один тип `dict` с дополнительной информацией.
{* ../../docs_src/schema_extra_example/tutorial004_an_py310.py hl[23:38] *}
Каждый конкретный пример типа `dict` в аргументе `examples` может содержать:
Когда вы делаете это, примеры становятся частью внутренней **JSON Schema** для данных тела запроса.
* `summary`: Краткое описание для примера.
* `description`: Полное описание, которое может содержать текст в формате Markdown.
* `value`: Это конкретный пример, который отображается, например, в виде типа `dict`.
* `externalValue`: альтернатива параметру `value`, URL-адрес, указывающий на пример. Хотя это может не поддерживаться таким же количеством инструментов разработки и тестирования API, как параметр `value`.
Тем не менее, на <abbr title="2023-08-26">момент написания этого</abbr> Swagger UI, инструмент, отвечающий за отображение UI документации, не поддерживает показ нескольких примеров для данных в **JSON Schema**. Но ниже есть обходной путь.
{* ../../docs_src/schema_extra_example/tutorial004_an_py310.py hl[23:49] *}
### Специфические для OpenAPI `examples` { #openapi-specific-examples }
### Аргумент "examples" в UI документации
Ещё до того как **JSON Schema** поддержала `examples`, в OpenAPI была поддержка другого поля, также называемого `examples`.
С аргументом `examples`, добавленным в `Body()`, страница документации `/docs` будет выглядеть так:
Эти **специфические для OpenAPI** `examples` находятся в другой секции спецификации OpenAPI. Они находятся в **подробностях для каждой операции пути (обработчика пути)**, а не внутри каждого объекта Schema.
И Swagger UI уже какое‑то время поддерживает именно это поле `examples`. Поэтому вы можете использовать его, чтобы **отобразить** разные **примеры в UI документации**.
Структура этого специфичного для OpenAPI поля `examples` — это `dict` с **несколькими примерами** (вместо `list`), каждый с дополнительной информацией, которая также будет добавлена в **OpenAPI**.
Это не помещается внутрь каждого объекта Schema в OpenAPI, это находится снаружи, непосредственно на уровне самой *операции пути*.
### Использование параметра `openapi_examples` { #using-the-openapi-examples-parameter }
Вы можете объявлять специфические для OpenAPI `examples` в FastAPI с помощью параметра `openapi_examples` для:
* `Path()`
* `Query()`
* `Header()`
* `Cookie()`
* `Body()`
* `Form()`
* `File()`
Ключи `dict` идентифицируют каждый пример, а каждое значение — это ещё один `dict`.
Каждый конкретный пример‑`dict` в `examples` может содержать:
* `summary`: Краткое описание примера.
* `description`: Подробное описание, которое может содержать текст в Markdown.
* `value`: Это фактический пример, который отображается, например, `dict`.
* `externalValue`: альтернатива `value`, URL, указывающий на пример. Хотя это может поддерживаться не так многими инструментами, как `value`.
Использовать это можно так:
{* ../../docs_src/schema_extra_example/tutorial005_an_py310.py hl[23:49] *}
### OpenAPI-примеры в UI документации { #openapi-examples-in-the-docs-ui }
С `openapi_examples`, добавленным в `Body()`, страница `/docs` будет выглядеть так:
<img src="/img/tutorial/body-fields/image02.png">
## Технические Детали
## Технические детали { #technical-details }
/// warning | Внимание
/// tip | Подсказка
Эти технические детали относятся к стандартам **JSON Schema** и **OpenAPI**.
Если вы уже используете **FastAPI** версии **0.99.0 или выше**, вы, вероятно, можете **пропустить** эти подробности.
Если предложенные выше идеи уже работают для вас, возможно этого будет достаточно и эти детали вам не потребуются, можете спокойно их пропустить.
Они более актуальны для старых версий, до того как стала доступна OpenAPI 3.1.0.
Считайте это кратким **уроком истории** про OpenAPI и JSON Schema. 🤓
///
Когда вы добавляете пример внутрь модели Pydantic, используя `schema_extra` или `Field(example="something")`, этот пример добавляется в **JSON Schema** для данной модели Pydantic.
/// warning | Внимание
Далее идут очень технические подробности о стандартах **JSON Schema** и **OpenAPI**.
Если идеи выше уже работают для вас, этого может быть достаточно, и, вероятно, вам не нужны эти детали — смело пропускайте их.
///
До OpenAPI 3.1.0 OpenAPI использовала более старую и модифицированную версию **JSON Schema**.
В JSON Schema не было `examples`, поэтому OpenAPI добавила собственное поле `example` в свою модифицированную версию.
OpenAPI также добавила поля `example` и `examples` в другие части спецификации:
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#parameter-object" class="external-link" target="_blank">`Parameter Object` (в спецификации)</a>, которое использовалось в FastAPI:
* `Path()`
* `Query()`
* `Header()`
* `Cookie()`
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#media-type-object" class="external-link" target="_blank">`Request Body Object`, в поле `content`, в `Media Type Object` (в спецификации)</a>, которое использовалось в FastAPI:
* `Body()`
* `File()`
* `Form()`
/// info | Информация
Этот старый специфичный для OpenAPI параметр `examples` теперь называется `openapi_examples`, начиная с FastAPI `0.103.0`.
///
### Поле `examples` в JSON Schema { #json-schemas-examples-field }
Позже в новой версии спецификации JSON Schema было добавлено поле <a href="https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.9.5" class="external-link" target="_blank">`examples`</a>.
А затем новый OpenAPI 3.1.0 был основан на последней версии (JSON Schema 2020-12), которая включала это новое поле `examples`.
И теперь это новое поле `examples` имеет приоритет над старым одиночным (и кастомным) полем `example`, которое теперь устарело.
Это новое поле `examples` в JSON Schema — это **просто `list`** примеров, а не dict с дополнительными метаданными, как в других местах OpenAPI (описанных выше).
/// info | Информация
Даже после того как OpenAPI 3.1.0 была выпущена с этой новой, более простой интеграцией с JSON Schema, какое‑то время Swagger UI, инструмент, предоставляющий автоматическую документацию, не поддерживал OpenAPI 3.1.0 (поддержка появилась начиная с версии 5.0.0 🎉).
Из‑за этого версии FastAPI до 0.99.0 всё ещё использовали версии OpenAPI ниже 3.1.0.
///
### `examples` в Pydantic и FastAPI { #pydantic-and-fastapi-examples }
Когда вы добавляете `examples` внутри модели Pydantic, используя `schema_extra` или `Field(examples=["something"])`, этот пример добавляется в **JSON Schema** для этой модели Pydantic.
И эта **JSON Schema** модели Pydantic включается в **OpenAPI** вашего API, а затем используется в UI документации.
Поля `example` как такового не существует в стандартах **JSON Schema**. В последних версиях JSON-схемы определено поле <a href="https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.9.5" class="external-link" target="_blank">`examples`</a>, но OpenAPI 3.0.3 основан на более старой версии JSON-схемы, которая не имела поля `examples`.
В версиях FastAPI до 0.99.0 (0.99.0 и выше используют новый OpenAPI 3.1.0), когда вы использовали `example` или `examples` с любыми другими утилитами (`Query()`, `Body()`, и т.д.), эти примеры не добавлялись в JSON Schema, описывающую эти данные (даже в собственную версию JSON Schema OpenAPI), они добавлялись непосредственно в объявление *операции пути* в OpenAPI (вне частей OpenAPI, использующих JSON Schema).
Таким образом, OpenAPI 3.0.3 определяет своё собственное поле <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#fixed-fields-20" class="external-link" target="_blank">`example`</a> для модифицированной версии **JSON Schema**, которую он использует чтобы достичь той же цели (однако это именно поле `example`, а не `examples`), и именно это используется API в UI документации (с интеграцией Swagger UI).
Но теперь, когда FastAPI 0.99.0 и выше используют OpenAPI 3.1.0, который использует JSON Schema 2020-12, а также Swagger UI 5.0.0 и выше, всё стало более последовательным, и примеры включаются в JSON Schema.
Итак, хотя поле `example` не является частью JSON-схемы, оно является частью настраиваемой версии JSON-схемы в OpenAPI, и именно это поле будет использоваться в UI документации.
### Swagger UI и специфичные для OpenAPI `examples` { #swagger-ui-and-openapi-specific-examples }
Однако, когда вы используете поле `example` или `examples` с любой другой функцией (`Query()`, `Body()`, и т.д.), эти примеры не добавляются в JSON-схему, которая описывает эти данные (даже в собственную версию JSON-схемы OpenAPI), они добавляются непосредственно в объявление *операции пути* в OpenAPI (вне частей OpenAPI, которые используют JSON-схему).
Раньше, поскольку Swagger UI не поддерживал несколько примеров JSON Schema (по состоянию на 2023-08-26), у пользователей не было способа показать несколько примеров в документации.
Для функций `Path()`, `Query()`, `Header()`, и `Cookie()`, аргументы `example` или `examples` добавляются в <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#parameter-object" class="external-link" target="_blank">определение OpenAPI, к объекту `Parameter Object` (в спецификации)</a>.
Чтобы решить это, FastAPI `0.103.0` **добавил поддержку** объявления того же старого, **специфичного для OpenAPI**, поля `examples` с новым параметром `openapi_examples`. 🤓
И для функций `Body()`, `File()` и `Form()` аргументы `example` или `examples` аналогично добавляются в <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#mediaTypeObject" class="external-link" target="_blank"> определение OpenAPI, к объекту `Request Body Object`, в поле `content` в объекте `Media Type Object` (в спецификации)</a>.
### Итог { #summary }
С другой стороны, существует более новая версия OpenAPI: **3.1.0**, недавно выпущенная. Она основана на последней версии JSON-схемы и большинство модификаций из OpenAPI JSON-схемы удалены в обмен на новые возможности из последней версии JSON-схемы, так что все эти мелкие отличия устранены. Тем не менее, Swagger UI в настоящее время не поддерживает OpenAPI 3.1.0, поэтому пока лучше продолжать использовать вышеупомянутые методы.
Раньше я говорил, что не очень люблю историю... а теперь вот рассказываю «уроки технической истории». 😅
Коротко: **обновитесь до FastAPI 0.99.0 или выше** — так всё будет значительно **проще, последовательнее и интуитивнее**, и вам не придётся знать все эти исторические подробности. 😎

View File

@@ -1,52 +1,58 @@
# Безопасность - первые шаги
# Безопасность первые шаги { #security-first-steps }
Представим, что у вас есть свой **бэкенд** API на некотором домене.
Представим, что у вас есть **бэкенд** API на некотором домене.
И у вас есть **фронтенд** на другом домене или на другом пути того же домена (или в мобильном приложении).
И вы хотите иметь возможность аутентификации фронтенда с бэкендом, используя **имя пользователя** и **пароль**.
И вы хотите, чтобы фронтенд мог аутентифицироваться на бэкенде, используя **имя пользователя** и **пароль**.
Мы можем использовать **OAuth2** для создания такой системы с помощью **FastAPI**.
Мы можем использовать **OAuth2**, чтобы построить это с **FastAPI**.
Но давайте избавим вас от необходимости читать всю длинную спецификацию, чтобы найти те небольшие кусочки информации, которые вам нужны.
Но давайте сэкономим вам время на чтение всей длинной спецификации в поисках тех небольших фрагментов информации, которые вам нужны.
Для работы с безопасностью воспользуемся средствами, предоставленными **FastAPI**.
Воспользуемся инструментами, предоставленными **FastAPI**, чтобы работать с безопасностью.
## Как это выглядит
## Как это выглядит { #how-it-looks }
Давайте сначала просто воспользуемся кодом и посмотрим, как он работает, а затем детально разберём, что происходит.
Сначала просто воспользуемся кодом и посмотрим, как он работает, а затем вернемся и разберемся, что происходит.
## Создание `main.py`
## Создание `main.py` { #create-main-py }
Скопируйте пример в файл `main.py`:
{* ../../docs_src/security/tutorial001_an_py39.py *}
## Запуск
## Запуск { #run-it }
/// info | Дополнительная информация
Вначале, установите библиотеку <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>.
Пакет <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a> автоматически устанавливается вместе с **FastAPI**, если вы запускаете команду `pip install "fastapi[standard]"`.
А именно: `pip install python-multipart`.
Однако, если вы используете команду `pip install fastapi`, пакет `python-multipart` по умолчанию не включается.
Это связано с тем, что **OAuth2** использует "данные формы" для передачи `имени пользователя` и `пароля`.
Чтобы установить его вручную, убедитесь, что вы создали [виртуальное окружение](../../virtual-environments.md){.internal-link target=_blank}, активировали его и затем установили пакет:
```console
$ pip install python-multipart
```
Это связано с тем, что **OAuth2** использует «данные формы» для отправки `username` и `password`.
///
Запустите ваш сервер:
Запустите пример командой:
<div class="termy">
```console
$ uvicorn main:app --reload
$ fastapi dev main.py
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
## Проверка
## Проверка { #check-it }
Перейдите к интерактивной документации по адресу: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
@@ -56,73 +62,75 @@ $ uvicorn main:app --reload
/// check | Кнопка авторизации!
У вас уже появилась новая кнопка "Authorize".
У вас уже появилась новая кнопка «Authorize».
А у *операции пути* теперь появился маленький замочек в правом верхнем углу, на который можно нажать.
А у вашей *операции пути* в правом верхнем углу есть маленький замочек, на который можно нажать.
///
При нажатии на нее появляется небольшая форма авторизации, в которую нужно ввести `имя пользователя` и `пароль` (и другие необязательные поля):
Если нажать на нее, появится небольшая форма авторизации, в которую нужно ввести `username` и `password` (и другие необязательные поля):
<img src="/img/tutorial/security/image02.png">
/// note | Технические детали
/// note | Примечание
Неважно, что вы введете в форму, она пока не будет работать. Но мы к этому еще придем.
Неважно, что вы введете в форму пока это не будет работать. Но мы скоро до этого дойдем.
///
Конечно, это не фронтенд для конечных пользователей, но это отличный автоматический инструмент для интерактивного документирования всех ваших API.
Конечно, это не фронтенд для конечных пользователей, но это отличный автоматический инструмент для интерактивного документирования всего вашего API.
Он может использоваться командой фронтенда (которой можете быть и вы сами).
Им может пользоваться команда фронтенда (которой можете быть и вы сами).
Он может быть использован сторонними приложениями и системами.
Им могут пользоваться сторонние приложения и системы.
Кроме того, его можно использовать самостоятельно для отладки, проверки и тестирования одного и того же приложения.
И им также можете пользоваться вы сами, чтобы отлаживать, проверять и тестировать то же самое приложение.
## Аутентификация по паролю
## «`password` flow» (аутентификация по паролю) { #the-password-flow }
Теперь давайте вернемся немного назад и разберемся, что же это такое.
Теперь давайте немного вернемся и разберемся, что это все такое.
Аутентификация по паролю является одним из способов, определенных в OAuth2, для обеспечения безопасности и аутентификации.
«`password` flow» — это один из способов («flows»), определенных в OAuth2, для обеспечения безопасности и аутентификации.
OAuth2 был разработан для того, чтобы бэкэнд или API были независимы от сервера, который аутентифицирует пользователя.
OAuth2 был спроектирован так, чтобы бэкенд или API были независимы от сервера, который аутентифицирует пользователя.
Но в нашем случае одно и то же приложение **FastAPI** будет работать с API и аутентификацией.
Но в нашем случае одно и то же приложение **FastAPI** будет работать и с API, и с аутентификацией.
Итак, рассмотрим его с этой упрощенной точки зрения:
Итак, рассмотрим это с упрощенной точки зрения:
* Пользователь вводит на фронтенде `имя пользователя` и `пароль` и нажимает `Enter`.
* Фронтенд (работающий в браузере пользователя) отправляет эти `имя пользователя` и `пароль` на определенный URL в нашем API (объявленный с помощью параметра `tokenUrl="token"`).
* API проверяет эти `имя пользователя` и `пароль` и выдает в ответ "токен" (мы еще не реализовали ничего из этого).
* "Токен" - это просто строка с некоторым содержимым, которое мы можем использовать позже для верификации пользователя.
* Обычно срок действия токена истекает через некоторое время.
* Таким образом, пользователю придется снова войти в систему в какой-то момент времени.
* И если токен будет украден, то риск будет меньше, так как он не похож на постоянный ключ, который будет работать вечно (в большинстве случаев).
* Фронтенд временно хранит этот токен в каком-то месте.
* Пользователь щелкает мышью на фронтенде, чтобы перейти в другой раздел на фронтенде.
* Фронтенду необходимо получить дополнительные данные из API.
* Но для этого необходима аутентификация для конкретной конечной точки.
* Поэтому для аутентификации в нашем API он посылает заголовок `Authorization` со значением `Bearer` плюс сам токен.
* Если токен содержит `foobar`, то содержание заголовка `Authorization` будет таким: `Bearer foobar`.
* Пользователь вводит на фронтенде `username` и `password` и нажимает `Enter`.
* Фронтенд (работающий в браузере пользователя) отправляет эти `username` и `password` на конкретный URL в нашем API (объявленный с `tokenUrl="token"`).
* API проверяет этот `username` и `password` и отвечает «токеном» (мы еще ничего из этого не реализовали).
* «Токен» — это просто строка с некоторым содержимым, которое мы сможем позже использовать для проверки этого пользователя.
* Обычно у токена установлен срок действия: он истекает через некоторое время.
* Поэтому пользователю придется снова войти в систему в какойто момент.
* И если токен украдут, риск меньше: это не постоянный ключ, который будет работать вечно (в большинстве случаев).
* Фронтенд временно где‑то хранит этот токен.
* Пользователь кликает во фронтенде, чтобы перейти в другой раздел веб‑приложения.
* Фронтенду нужно получить дополнительные данные из API.
* Но для этого для конкретной конечной точки нужна аутентификация.
* Поэтому, чтобы аутентифицироваться в нашем API, он отправляет HTTP-заголовок `Authorization` со значением `Bearer ` плюс сам токен.
* Если токен содержит `foobar`, то содержимое заголовка `Authorization` будет: `Bearer foobar`.
## Класс `OAuth2PasswordBearer` в **FastAPI**
## Класс `OAuth2PasswordBearer` в **FastAPI** { #fastapis-oauth2passwordbearer }
**FastAPI** предоставляет несколько средств на разных уровнях абстракции для реализации этих функций безопасности.
В данном примере мы будем использовать **OAuth2**, с аутентификацией по паролю, используя токен **Bearer**. Для этого мы используем класс `OAuth2PasswordBearer`.
В этом примере мы будем использовать **OAuth2**, с потоком **Password**, используя токен **Bearer**. Для этого мы используем класс `OAuth2PasswordBearer`.
/// info | Дополнительная информация
Токен "bearer" - не единственный вариант, но для нашего случая он является наилучшим.
Токен «bearer» — не единственный вариант.
И это может быть лучшим вариантом для большинства случаев использования, если только вы не являетесь экспертом в области OAuth2 и точно знаете, почему вам лучше подходит какой-то другой вариант.
Но для нашего случая он — лучший.
В этом случае **FastAPI** также предоставляет инструменты для его реализации.
И он может быть лучшим для большинства случаев использования, если только вы не являетесь экспертом по OAuth2 и точно знаете, почему другой вариант лучше подходит под ваши нужды.
В этом случае **FastAPI** также предоставляет инструменты, чтобы его реализовать.
///
При создании экземпляра класса `OAuth2PasswordBearer` мы передаем в него параметр `tokenUrl`. Этот параметр содержит URL, который клиент (фронтенд, работающий в браузере пользователя) будет использовать для отправки `имени пользователя` и `пароля` с целью получения токена.
При создании экземпляра класса `OAuth2PasswordBearer` мы передаем параметр `tokenUrl`. Этот параметр содержит URL, который клиент (фронтенд, работающий в браузере пользователя) будет использовать для отправки `username` и `password`, чтобы получить токен.
{* ../../docs_src/security/tutorial001_an_py39.py hl[8] *}
@@ -130,27 +138,27 @@ OAuth2 был разработан для того, чтобы бэкэнд ил
Здесь `tokenUrl="token"` ссылается на относительный URL `token`, который мы еще не создали. Поскольку это относительный URL, он эквивалентен `./token`.
Поскольку мы используем относительный URL, если ваш API расположен по адресу `https://example.com/`, то он будет ссылаться на `https://example.com/token`. Если же ваш API расположен по адресу `https://example.com/api/v1/`, то он будет ссылаться на `https://example.com/api/v1/token`.
Поскольку мы используем относительный URL, если ваш API расположен по адресу `https://example.com/`, то он будет ссылаться на `https://example.com/token`. А если ваш API расположен по адресу `https://example.com/api/v1/`, то он будет ссылаться на `https://example.com/api/v1/token`.
Использование относительного URL важно для того, чтобы ваше приложение продолжало работать даже в таких сложных случаях, как оно находится [за прокси-сервером](../../advanced/behind-a-proxy.md){.internal-link target=_blank}.
Использование относительного URL важно для того, чтобы ваше приложение продолжало работать даже в таком продвинутом случае, как [За прокси-сервером](../../advanced/behind-a-proxy.md){.internal-link target=_blank}.
///
Этот параметр не создает конечную точку / *операцию пути*, а объявляет, что URL `/token` будет таким, который клиент должен использовать для получения токена. Эта информация используется в OpenAPI, а затем в интерактивных системах документации API.
Этот параметр не создает конечную точку / *операцию пути*, а объявляет, что URL `/token` — это тот, который клиент должен использовать для получения токена. Эта информация используется в OpenAPI, а затем в интерактивных системах документации по API.
Вскоре мы создадим и саму операцию пути.
Скоро мы также создадим и саму операцию пути.
/// info | Дополнительная информация
Если вы очень строгий "питонист", то вам может не понравиться стиль названия параметра `tokenUrl` вместо `token_url`.
Если вы очень строгий «питонист», вам может не понравиться стиль имени параметра `tokenUrl` вместо `token_url`.
Это связано с тем, что тут используется то же имя, что и в спецификации OpenAPI. Таким образом, если вам необходимо более подробно изучить какую-либо из этих схем безопасности, вы можете просто использовать копирование/вставку, чтобы найти дополнительную информацию о ней.
Это потому, что используется то же имя, что и в спецификации OpenAPI. Так, если вам нужно разобраться подробнее в какой‑то из этих схем безопасности, вы можете просто скопировать и вставить это имя, чтобы найти больше информации.
///
Переменная `oauth2_scheme` является экземпляром `OAuth2PasswordBearer`, но она также является "вызываемой".
Переменная `oauth2_scheme` — это экземпляр `OAuth2PasswordBearer`, но она также «вызываемая».
Ее можно вызвать следующим образом:
Ее можно вызвать так:
```Python
oauth2_scheme(some, parameters)
@@ -158,38 +166,38 @@ oauth2_scheme(some, parameters)
Поэтому ее можно использовать вместе с `Depends`.
### Использование
### Использование { #use-it }
Теперь вы можете передать ваш `oauth2_scheme` в зависимость с помощью `Depends`.
Теперь вы можете передать `oauth2_scheme` как зависимость с `Depends`.
{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *}
Эта зависимость будет предоставлять `строку`, которая присваивается параметру `token` в *функции операции пути*.
Эта зависимость предоставит `str`, который будет присвоен параметру `token` *функции-обработчика пути*.
**FastAPI** будет знать, что он может использовать эту зависимость для определения "схемы безопасности" в схеме OpenAPI (и автоматической документации по API).
**FastAPI** будет знать, что может использовать эту зависимость для определения «схемы безопасности» в схеме OpenAPI (и в автоматической документации по API).
/// info | Технические детали
**FastAPI** будет знать, что он может использовать класс `OAuth2PasswordBearer` (объявленный в зависимости) для определения схемы безопасности в OpenAPI, поскольку он наследуется от `fastapi.security.oauth2.OAuth2`, который, в свою очередь, наследуется от `fastapi.security.base.SecurityBase`.
**FastAPI** будет знать, что может использовать класс `OAuth2PasswordBearer` (объявленный в зависимости) для определения схемы безопасности в OpenAPI, потому что он наследуется от `fastapi.security.oauth2.OAuth2`, который, в свою очередь, наследуется от `fastapi.security.base.SecurityBase`.
Все утилиты безопасности, интегрируемые в OpenAPI (и автоматическая документация по API), наследуются от `SecurityBase`, поэтому **FastAPI** может знать, как интегрировать их в OpenAPI.
Все утилиты безопасности, интегрируемые с OpenAPI (и автоматической документацией по API), наследуются от `SecurityBase`, — так **FastAPI** понимает, как интегрировать их в OpenAPI.
///
## Что он делает
## Что он делает { #what-it-does }
Он будет искать в запросе заголовок `Authorization` и проверять, содержит ли он значение `Bearer` с некоторым токеном, и возвращать токен в виде `строки`.
Он будет искать в запросе заголовок `Authorization`, проверять, что его значение — это `Bearer ` плюс некоторый токен, и вернет токен как `str`.
Если он не видит заголовка `Authorization` или значение не имеет токена `Bearer`, то в ответ будет выдана ошибка с кодом состояния 401 (`UNAUTHORIZED`).
Если заголовок `Authorization` отсутствует или его значение не содержит токен `Bearer `, он сразу ответит ошибкой со статус-кодом 401 (`UNAUTHORIZED`).
Для возврата ошибки даже не нужно проверять, существует ли токен. Вы можете быть уверены, что если ваша функция была выполнена, то в этом токене есть `строка`.
Вам даже не нужно проверять наличие токена, чтобы вернуть ошибку. Вы можете быть уверены: если ваша функция была выполнена, в этом токене будет `str`.
Проверить это можно уже сейчас в интерактивной документации:
Это уже можно попробовать в интерактивной документации:
<img src="/img/tutorial/security/image03.png">
Мы пока не проверяем валидность токена, но для начала неплохо.
Мы пока не проверяем валидность токена, но для начала это уже неплохо.
## Резюме
## Резюме { #recap }
Таким образом, всего за 3-4 дополнительные строки вы получаете некую примитивную форму защиты.
Таким образом, всего за 34 дополнительные строки у вас уже есть некая примитивная форма защиты.

View File

@@ -1,99 +1,105 @@
# Данные текущего пользователя
# Получить текущего пользователя { #get-current-user }
В предыдущей главе система безопасности (основанная на системе внедрения зависимостей) передавала *функции, обрабатывающей эндпоинт,* `токен` в виде `строки`:
В предыдущей главе система безопасности (основанная на системе внедрения зависимостей) передавала *функции-обработчику пути* `token` типа `str`:
{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *}
Это пока что не слишком нам полезно. Давайте изменим код так, чтобы он возвращал нам данные пользователя, отправившего запрос.
Но это всё ещё не слишком полезно.
## Создание модели пользователя
Сделаем так, чтобы она возвращала текущего пользователя.
## Создать модель пользователя { #create-a-user-model }
Сначала создадим Pydantic-модель пользователя.
Точно так же, как мы использовали Pydantic для объявления тел запросов, мы можем использовать его где угодно:
Точно так же, как мы используем Pydantic для объявления тел запросов, мы можем использовать его где угодно:
{* ../../docs_src/security/tutorial002_an_py310.py hl[5,12:6] *}
## Создание зависимости `get_current_user`
## Создать зависимость `get_current_user` { #create-a-get-current-user-dependency }
Давайте создадим зависимость `get_current_user`.
Помните, что у зависимостей могут быть подзависимости?
`get_current_user` как раз будет иметь подзависимость `oauth2_scheme`, которую мы создали ранее.
`get_current_user` будет иметь зависимость от того же `oauth2_scheme`, который мы создали ранее.
Аналогично тому, как мы делали это ранее в *обработчике эндпоинта* наша новая зависимость `get_current_user` будет получать `token` в виде `строки` от подзависимости `oauth2_scheme`:
Аналогично тому, как мы делали ранее прямо в *операции пути*, новая зависимость `get_current_user` получит `token` типа `str` от подзависимости `oauth2_scheme`:
{* ../../docs_src/security/tutorial002_an_py310.py hl[25] *}
## Получение данных пользователя
## Получить пользователя { #get-the-user }
`get_current_user` будет использовать созданную нами (ненастоящую) служебную функцию, которая принимает токен в виде `строки` и возвращает нашу Pydantic-модель `User`:
`get_current_user` будет использовать созданную нами (ненастоящую) служебную функцию, которая принимает токен типа `str` и возвращает нашу Pydantic-модель `User`:
{* ../../docs_src/security/tutorial002_an_py310.py hl[19:22,26:27] *}
## Внедрение зависимости текущего пользователя
## Внедрить текущего пользователя { #inject-the-current-user }
Теперь мы можем использовать тот же `Depends` с нашей зависимостью `get_current_user` в *функции обрабатывающей эндпоинт*:
Теперь мы можем использовать тот же `Depends` с нашей `get_current_user` в *операции пути*:
{* ../../docs_src/security/tutorial002_an_py310.py hl[31] *}
Обратите внимание, что мы объявляем тип переменной `current_user` как Pydantic-модель `User`.
Обратите внимание, что мы объявляем тип `current_user` как Pydantic-модель `User`.
Это поможет выполнить внутри функции все проверки автозаполнения и типа.
Это поможет внутри функции с автозавершением и проверками типов.
/// tip | Подсказка
Возможно, вы помните, что тело запроса также объявляется с помощью Pydantic-моделей.
В этом месте у **FastAPI** не возникнет проблем, потому что вы используете `Depends`.
Возможно, вы помните, что тела запросов также объявляются с помощью Pydantic-моделей.
Здесь **FastAPI** не запутается, потому что вы используете `Depends`.
///
/// check | Заметка
То, как устроена эта система зависимостей, позволяет нам иметь различные зависимости, которые возвращают модель `User`.
Мы не ограничены наличием только одной зависимости, которая может возвращать данные такого типа.
То, как устроена эта система зависимостей, позволяет иметь разные зависимости, которые возвращают модель `User`.
Мы не ограничены наличием только одной зависимости, которая может возвращать такой тип данных.
///
## Другие модели
## Другие модели { #other-models }
Теперь вы можете получать информацию о текущем пользователе непосредственно в *функции обрабатывающей эндпоинт* и работать с механизмами безопасности на уровне **Внедрения Зависимостей**, используя `Depends`.
Теперь вы можете получать текущего пользователя напрямую в *функциях-обработчиках пути* и работать с механизмами безопасности на уровне **внедрения зависимостей**, используя `Depends`.
Причем для обеспечения требований безопасности можно использовать любую модель или данные (в данном случае - Pydantic-модель `User`).
И вы можете использовать любую модель или данные для требований безопасности (в данном случае Pydantic-модель `User`).
Но вы не ограничены использованием какой-то конкретной моделью данных, классом или типом.
Но вы не ограничены использованием какой-то конкретной модели данных, класса или типа.
Вы хотите использовать в модели `id` и `email`, а `username` вам не нужен? Ну разумеется. Воспользуйтесь тем же инструментарием.
Хотите, чтобы в модели были `id` и `email`, но не было `username`? Пожалуйста. Можно использовать те же инструменты.
Вам нужны только `строки`? Или только `словари`? Или непосредственно экземпляр модели класса базы данных? Все это работает точно также.
Хотите просто `str`? Или просто `dict`? Или напрямую экземпляр класса модели базы данных? Всё работает одинаково.
У вас нет пользователей, которые входят в ваше приложение, а только роботы, боты или другие системы, у которых есть только токен доступа? Опять же, все работает одинаково.
У вас вообще нет пользователей, которые входят в приложение, а есть роботы, боты или другие системы, у которых есть только токен доступа? Снова — всё работает так же.
Просто используйте любую модель, любой класс, любую базу данных, которые нужны для вашего приложения. Система внедрения зависимостей **FastAPI** поможет вам в этом.
Просто используйте любую модель, любой класс, любую базу данных, которые нужны вашему приложению. Система внедрения зависимостей **FastAPI** поможет вам в этом.
## Размер кода
## Размер кода { #code-size }
Этот пример может показаться многословным. Следует иметь в виду, что в одном файле мы смешиваем безопасность, модели данных, служебные функции и *эндпоинты*.
Этот пример может показаться многословным. Имейте в виду, что в одном файле мы смешиваем безопасность, модели данных, служебные функции и *операции пути*.
Но вот ключевой момент:
Но вот ключевой момент.
Все, что касается безопасности и внедрения зависимостей, пишется один раз.
Всё, что касается безопасности и внедрения зависимостей, пишется один раз.
И вы можете сделать его настолько сложным, насколько захотите. И все это будет написано только один раз, в одном месте, со всей своей гибкостью.
И вы можете сделать это настолько сложным, насколько захотите. И всё равно это будет написано только один раз, в одном месте. Со всей гибкостью.
И у вас могут быть тысячи конечных точек (*эндпоинтов*), использующих одну и ту же систему безопасности.
При этом у вас могут быть тысячи эндпоинтов (*операций пути*), использующих одну и ту же систему безопасности.
И все они (или любая их часть по вашему желанию) могут воспользоваться преимуществами повторного использования этих зависимостей или любых других зависимостей, которые вы создадите.
И все эти тысячи *эндпоинтов* могут составлять всего 3 строки:
И все эти тысячи *операций пути* могут состоять всего из 3 строк:
{* ../../docs_src/security/tutorial002_an_py310.py hl[30:32] *}
## Резюме
## Резюме { #recap }
Теперь вы можете получать данные о текущем пользователе непосредственно в своей *функции обработчике эндпоинта*.
Теперь вы можете получать текущего пользователя прямо в своей *функции-обработчике пути*.
Мы уже на полпути к этому.
Мы уже на полпути.
Осталось лишь добавить *эндпоинт* для отправки пользователем/клиентом своих `имени пользователя` и `пароля`.
Нужно лишь добавить *операцию пути*, чтобы пользователь/клиент мог отправить `username` и `password`.
Это будет рассмотрено в следующем разделе.
Это будет дальше.

View File

@@ -1,12 +1,12 @@
# OAuth2 с паролем (и хешированием), Bearer с JWT-токенами
# OAuth2 с паролем (и хешированием), Bearer с JWT-токенами { #oauth2-with-password-and-hashing-bearer-with-jwt-tokens }
Теперь, когда у нас определен процесс обеспечения безопасности, давайте сделаем приложение действительно безопасным, используя токены <abbr title="JSON Web Tokens">JWT</abbr> и безопасное хеширование паролей.
Теперь, когда у нас определен процесс обеспечения безопасности, давайте сделаем приложение действительно безопасным, используя токены <abbr title="JSON Web Tokens веб‑токены JSON">JWT</abbr> и безопасное хеширование паролей.
Этот код можно реально использовать в своем приложении, сохранять хэши паролей в базе данных и т.д.
Мы продолжим разбираться, начиная с того места, на котором остановились в предыдущей главе.
## Про JWT
## Про JWT { #about-jwt }
JWT означает "JSON Web Tokens".
@@ -26,7 +26,7 @@ eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4
Если вы хотите поиграть с JWT-токенами и посмотреть, как они работают, посмотрите <a href="https://jwt.io/" class="external-link" target="_blank">https://jwt.io</a>.
## Установка `PyJWT`
## Установка `PyJWT` { #install-pyjwt }
Нам необходимо установить `pyjwt` для генерации и проверки JWT-токенов на языке Python.
@@ -45,10 +45,10 @@ $ pip install pyjwt
/// info | Дополнительная информация
Если вы планируете использовать алгоритмы цифровой подписи, такие как RSA или ECDSA, вам следует установить зависимость библиотеки криптографии `pyjwt[crypto]`.
Подробнее об этом можно прочитать в <a href=«https://pyjwt.readthedocs.io/en/latest/installation.html» class=«external-link» target=«_blank»>документации по установке PyJWT</a>.
Подробнее об этом можно прочитать в <a href="https://pyjwt.readthedocs.io/en/latest/installation.html" class="external-link" target="_blank">документации по установке PyJWT</a>.
///
## Хеширование паролей
## Хеширование паролей { #password-hashing }
"Хеширование" означает преобразование некоторого содержимого (в данном случае пароля) в последовательность байтов (просто строку), которая выглядит как тарабарщина.
@@ -56,26 +56,26 @@ $ pip install pyjwt
Но преобразовать тарабарщину обратно в пароль невозможно.
### Для чего нужно хеширование паролей
### Для чего нужно хеширование паролей { #why-use-password-hashing }
Если ваша база данных будет украдена, то вор не получит пароли пользователей в открытом виде, а только их хэши.
Таким образом, вор не сможет использовать этот пароль в другой системе (поскольку многие пользователи везде используют один и тот же пароль, это было бы опасно).
## Установка `passlib`
## Установка `pwdlib` { #install-pwdlib }
PassLib - это отличный пакет Python для работы с хэшами паролей.
pwdlib это отличный пакет Python для работы с хэшами паролей.
Он поддерживает множество безопасных алгоритмов хеширования и утилит для работы с ними.
Рекомендуемый алгоритм - "Bcrypt".
Рекомендуемый алгоритм "Argon2".
Убедитесь, что вы создали и активировали виртуальное окружение, и затем установите PassLib вместе с Bcrypt:
Убедитесь, что вы создали [виртуальное окружение](../../virtual-environments.md){.internal-link target=_blank}, активируйте его, и затем установите pwdlib вместе с Argon2:
<div class="termy">
```console
$ pip install "passlib[bcrypt]"
$ pip install "pwdlib[argon2]"
---> 100%
```
@@ -83,40 +83,40 @@ $ pip install "passlib[bcrypt]"
</div>
/// tip | Подсказка
С помощью `passlib` можно даже настроить его на чтение паролей, созданных **Django**, плагином безопасности **Flask** или многими другими библиотеками.
С помощью `pwdlib` можно даже настроить его на чтение паролей, созданных **Django**, плагином безопасности **Flask** или многими другими библиотеками.
Таким образом, вы сможете, например, совместно использовать одни и те же данные из приложения Django в базе данных с приложением FastAPI. Или постепенно мигрировать Django-приложение, используя ту же базу данных.
При этом пользователи смогут одновременно входить в систему как из приложения Django, так и из приложения **FastAPI**.
///
## Хеширование и проверка паролей
## Хеширование и проверка паролей { #hash-and-verify-the-passwords }
Импортируйте необходимые инструменты из `passlib`.
Импортируйте необходимые инструменты из `pwdlib`.
Создайте "контекст" PassLib. Именно он будет использоваться для хэширования и проверки паролей.
Создайте экземпляр PasswordHash с рекомендованными настройками — он будет использоваться для хэширования и проверки паролей.
/// tip | Подсказка
Контекст PassLib также имеет функциональность для использования различных алгоритмов хеширования, в том числе и устаревших, только для возможности их проверки и т.д.
pwdlib также поддерживает алгоритм хеширования bcrypt, но не включает устаревшие алгоритмы — для работы с устаревшими хэшами рекомендуется использовать библиотеку passlib.
Например, вы можете использовать его для чтения и проверки паролей, сгенерированных другой системой (например, Django), но хэшировать все новые пароли другим алгоритмом, например Bcrypt.
Например, вы можете использовать ее для чтения и проверки паролей, сгенерированных другой системой (например, Django), но хэшировать все новые пароли другим алгоритмом, например Argon2 или Bcrypt.
И при этом быть совместимым со всеми этими системами.
///
Создайте служебную функцию для хэширования пароля, поступающего от пользователя.
А затем создайте другую - для проверки соответствия полученного пароля и хранимого хэша.
А затем создайте другую для проверки соответствия полученного пароля и хранимого хэша.
И еще одну - для аутентификации и возврата пользователя.
И еще одну для аутентификации и возврата пользователя.
{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,56:57,60:61,70:76] *}
/// note | Технические детали
Если проверить новую (фальшивую) базу данных `fake_users_db`, то можно увидеть, как теперь выглядит хэшированный пароль: `"$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW"`.
Если проверить новую (фальшивую) базу данных `fake_users_db`, то можно увидеть, как теперь выглядит хэшированный пароль: `"$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc"`.
///
## Работа с JWT токенами
## Работа с JWT токенами { #handle-jwt-tokens }
Импортируйте установленные модули.
@@ -140,13 +140,13 @@ $ openssl rand -hex 32
Создайте переменную для срока действия токена.
Определите Pydantic Model, которая будет использоваться для формирования ответа на запрос на получение токена.
Определите Pydantic-модель, которая будет использоваться для формирования ответа на запрос на получение токена.
Создайте служебную функцию для генерации нового токена доступа.
{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,79:87] *}
## Обновление зависимостей
## Обновление зависимостей { #update-the-dependencies }
Обновите `get_current_user` для получения того же токена, что и раньше, но на этот раз с использованием JWT-токенов.
@@ -156,7 +156,7 @@ $ openssl rand -hex 32
{* ../../docs_src/security/tutorial004_an_py310.py hl[90:107] *}
## Обновление *операции пути* `/token`
## Обновление *операции пути* `/token` { #update-the-token-path-operation }
Создайте `timedelta` со временем истечения срока действия токена.
@@ -164,7 +164,7 @@ $ openssl rand -hex 32
{* ../../docs_src/security/tutorial004_an_py310.py hl[118:133] *}
### Технические подробности о JWT ключе `sub`
### Технические подробности о JWT ключе `sub` { #technical-details-about-the-jwt-subject-sub }
В спецификации JWT говорится, что существует ключ `sub`, содержащий субъект токена.
@@ -186,7 +186,7 @@ JWT может использоваться и для других целей,
Важно помнить, что ключ `sub` должен иметь уникальный идентификатор для всего приложения и представлять собой строку.
## Проверка в действии
## Проверка в действии { #check-it }
Запустите сервер и перейдите к документации: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
@@ -201,7 +201,7 @@ JWT может использоваться и для других целей,
Username: `johndoe`
Password: `secret`
/// check | Заметка
/// check | Проверка
Обратите внимание, что нигде в коде не используется открытый текст пароля "`secret`", мы используем только его хэшированную версию.
///
@@ -225,10 +225,10 @@ Password: `secret`
<img src="/img/tutorial/security/image10.png">
/// note | Техническая информация
Обратите внимание на заголовок `Authorization`, значение которого начинается с `Bearer`.
Обратите внимание на HTTP-заголовок `Authorization`, значение которого начинается с `Bearer `.
///
## Продвинутое использование `scopes`
## Продвинутое использование `scopes` { #advanced-usage-with-scopes }
В OAuth2 существует понятие "диапазоны" ("`scopes`").
@@ -236,9 +236,9 @@ Password: `secret`
Затем вы можете передать этот токен непосредственно пользователю или третьей стороне для взаимодействия с вашим API с определенным набором ограничений.
О том, как их использовать и как они интегрированы в **FastAPI**, читайте далее в **Руководстве пользователя**.
О том, как их использовать и как они интегрированы в **FastAPI**, читайте далее в **Расширенном руководстве пользователя**.
## Резюме
## Резюме { #recap }
С учетом того, что вы видели до сих пор, вы можете создать безопасное приложение **FastAPI**, используя такие стандарты, как OAuth2 и JWT.
@@ -252,10 +252,10 @@ Password: `secret`
Он предоставляет вам полную свободу действий, позволяя выбирать то, что лучше всего подходит для вашего проекта.
Вы можете напрямую использовать многие хорошо поддерживаемые и широко распространенные пакеты, такие как `passlib` и `PyJWT`, поскольку **FastAPI** не требует сложных механизмов для интеграции внешних пакетов.
Вы можете напрямую использовать многие хорошо поддерживаемые и широко распространенные пакеты, такие как `pwdlib` и `PyJWT`, поскольку **FastAPI** не требует сложных механизмов для интеграции внешних пакетов.
Напротив, он предоставляет инструменты, позволяющие максимально упростить этот процесс без ущерба для гибкости, надежности и безопасности.
При этом вы можете использовать и реализовывать безопасные стандартные протоколы, такие как OAuth2, относительно простым способом.
В **Руководстве пользователя** вы можете узнать больше о том, как использовать "диапазоны" ("`scopes`") OAuth2 для создания более точно настроенной системы разрешений в соответствии с теми же стандартами. OAuth2 с диапазонами - это механизм, используемый многими крупными провайдерами сервиса аутентификации, такими как Facebook, Google, GitHub, Microsoft, X (Twitter) и др., для авторизации сторонних приложений на взаимодействие с их API от имени их пользователей.
В **Расширенном руководстве пользователя** вы можете узнать больше о том, как использовать "диапазоны" ("`scopes`") OAuth2 для создания более точно настроенной системы разрешений в соответствии с теми же стандартами. OAuth2 с диапазонами это механизм, используемый многими крупными провайдерами сервиса аутентификации, такими как Facebook, Google, GitHub, Microsoft, X (Twitter) и др., для авторизации сторонних приложений на взаимодействие с их API от имени их пользователей.

View File

@@ -1,59 +1,58 @@
# Простая авторизация по протоколу OAuth2 с токеном типа Bearer
# Простая авторизация OAuth2 с паролем и «Bearer» { #simple-oauth2-with-password-and-bearer }
Теперь, отталкиваясь от предыдущей главы, добавим недостающие части, чтобы получить безопасную систему.
Теперь, отталкиваясь от предыдущей главы, добавим недостающие части, чтобы получить полный поток безопасности.
## Получение `имени пользователя` и `пароля`
## Получение `username` и `password` { #get-the-username-and-password }
Для получения `имени пользователя` и `пароля` мы будем использовать утилиты безопасности **FastAPI**.
Для получения `username` и `password` мы будем использовать утилиты безопасности **FastAPI**.
Протокол OAuth2 определяет, что при использовании "аутентификации по паролю" (которую мы и используем) клиент/пользователь должен передавать поля `username` и `password` в полях формы.
OAuth2 определяет, что при использовании "password flow" (аутентификация по паролю - именно его мы используем) клиент/пользователь должен передавать поля `username` и `password` в полях формы.
В спецификации сказано, что поля должны быть названы именно так. Поэтому `user-name` или `email` работать не будут.
Но не волнуйтесь, вы можете показать его конечным пользователям во фронтенде в том виде, в котором хотите.
Но не волнуйтесь, вы можете показать это конечным пользователям во фронтенде в том виде, в котором хотите.
А ваши модели баз данных могут использовать любые другие имена.
Но при авторизации согласно спецификации, требуется использовать именно эти имена, что даст нам возможность воспользоваться встроенной системой документации API.
Но для логин-операции пути нам нужно использовать именно эти имена, чтобы быть совместимыми со спецификацией (и иметь возможность, например, использовать встроенную систему документации API).
В спецификации также указано, что `username` и `password` должны передаваться в виде данных формы (так что никакого JSON здесь нет).
### Oбласть видимости (scope)
### `scope` { #scope }
В спецификации также говорится, что клиент может передать еще одно поле формы "`scope`".
В спецификации также говорится, что клиент может передать еще одно поле формы `scope`.
Имя поля формы - `scope` (в единственном числе), но на самом деле это длинная строка, состоящая из отдельных областей видимости (scopes), разделенных пробелами.
Имя поля формы `scope` (в единственном числе), но на самом деле это длинная строка, состоящая из отдельных "scopes", разделенных пробелами.
Каждая "область видимости" (scope) - это просто строка (без пробелов).
Каждый "scope" — это просто строка (без пробелов).
Обычно они используются для указания уровней доступа, например:
* `users:read` или `users:write` являются распространенными примерами.
* `users:read` или `users:write` распространенные примеры.
* `instagram_basic` используется Facebook / Instagram.
* `https://www.googleapis.com/auth/drive` используется компанией Google.
* `https://www.googleapis.com/auth/drive` используется Google.
/// info | Дополнительнаяя информация
В OAuth2 "scope" - это просто строка, которая уточняет уровень доступа.
/// info | Дополнительная информация
В OAuth2 "scope" это просто строка, которая указывает требуемое конкретное разрешение.
Не имеет значения, содержит ли он другие символы, например `:`, или является ли он URL.
Не имеет значения, содержит ли она другие символы, например `:`, или является ли это URL.
Эти детали зависят от конкретной реализации.
Эти детали зависят от реализации.
Для OAuth2 это просто строки.
///
## Код получения `имени пользователя` и `пароля`
## Код для получения `username` и `password` { #code-to-get-the-username-and-password }
Для решения задачи давайте воспользуемся утилитами, предоставляемыми **FastAPI**.
Теперь воспользуемся утилитами, предоставляемыми **FastAPI**, чтобы обработать это.
### `OAuth2PasswordRequestForm`
Сначала импортируйте `OAuth2PasswordRequestForm` и затем используйте ее как зависимость с `Depends` в *эндпоинте* `/token`:
### `OAuth2PasswordRequestForm` { #oauth2passwordrequestform }
Сначала импортируйте `OAuth2PasswordRequestForm` и затем используйте её как зависимость с `Depends` в операции пути для `/token`:
{* ../../docs_src/security/tutorial003_an_py310.py hl[4,78] *}
`OAuth2PasswordRequestForm` - это класс для использования в качестве зависимости для *функции обрабатывающей эндпоинт*, который определяет тело формы со следующими полями:
`OAuth2PasswordRequestForm` это зависимость-класс, которая объявляет тело формы со следующими полями:
* `username`.
* `password`.
@@ -61,51 +60,51 @@
* Необязательное поле `grant_type`.
/// tip | Подсказка
По спецификации OAuth2 поле `grant_type` является обязательным и содержит фиксированное значение `password`, но `OAuth2PasswordRequestForm` не обеспечивает этого.
По спецификации OAuth2 поле `grant_type` обязательно и содержит фиксированное значение `password`, но `OAuth2PasswordRequestForm` это не проверяет строго.
Если вам необходимо использовать `grant_type`, воспользуйтесь `OAuth2PasswordRequestFormStrict` вместо `OAuth2PasswordRequestForm`.
Если вам нужно это строгое требование, используйте `OAuth2PasswordRequestFormStrict` вместо `OAuth2PasswordRequestForm`.
///
* Необязательное поле `client_id` (в нашем примере он не нужен).
* Необязательное поле `client_secret` (в нашем примере он не нужен).
* Необязательное поле `client_id` (в нашем примере оно не нужно).
* Необязательное поле `client_secret` (в нашем примере оно не нужно).
/// info | Дополнительная информация
Форма `OAuth2PasswordRequestForm` не является специальным классом для **FastAPI**, как `OAuth2PasswordBearer`.
`OAuth2PasswordRequestForm` — это не специальный класс для **FastAPI**, как `OAuth2PasswordBearer`.
`OAuth2PasswordBearer` указывает **FastAPI**, что это схема безопасности. Следовательно, она будет добавлена в OpenAPI.
`OAuth2PasswordBearer` сообщает **FastAPI**, что это схема безопасности. Поэтому она добавляется в OpenAPI соответствующим образом.
Но `OAuth2PasswordRequestForm` - это всего лишь класс зависимости, который вы могли бы написать самостоятельно или вы могли бы объявить параметры `Form` напрямую.
А `OAuth2PasswordRequestForm` это просто зависимость-класс, которую вы могли бы написать сами, или вы могли бы объявить параметры `Form` напрямую.
Но, поскольку это распространённый вариант использования, он предоставляется **FastAPI** напрямую, просто чтобы облегчить задачу.
Но так как это распространённый вариант использования, он предоставлен **FastAPI** напрямую, чтобы упростить задачу.
///
### Использование данных формы
### Использование данных формы { #use-the-form-data }
/// tip | Подсказка
В экземпляре зависимого класса `OAuth2PasswordRequestForm` атрибут `scope`, состоящий из одной длинной строки, разделенной пробелами, заменен на атрибут `scopes`, состоящий из списка отдельных строк, каждая из которых соответствует определенному уровню доступа.
У экземпляра зависимости `OAuth2PasswordRequestForm` не будет атрибута `scope` с длинной строкой, разделенной пробелами. Вместо этого будет атрибут `scopes` со списком отдельных строк — по одной для каждого переданного scope.
В данном примере мы не используем `scopes`, но если вам это необходимо, то такая функциональность имеется.
В данном примере мы не используем `scopes`, но если вам это необходимо, функциональность есть.
///
Теперь получим данные о пользователе из (ненастоящей) базы данных, используя `username` из поля формы.
Если такого пользователя нет, то мы возвращаем ошибку "неверное имя пользователя или пароль".
Если такого пользователя нет, то мы возвращаем ошибку "Incorrect username or password" (неверное имя пользователя или пароль).
Для ошибки мы используем исключение `HTTPException`:
Для ошибки используем исключение `HTTPException`:
{* ../../docs_src/security/tutorial003_an_py310.py hl[3,79:81] *}
### Проверка пароля
### Проверка пароля { #check-the-password }
На данный момент у нас есть данные о пользователе из нашей базы данных, но мы еще не проверили пароль.
Давайте сначала поместим эти данные в модель Pydantic `UserInDB`.
Давайте сначала поместим эти данные в Pydantic-модель `UserInDB`.
Ни в коем случае нельзя сохранять пароли в открытом виде, поэтому мы будем использовать (пока что ненастоящую) систему хеширования паролей.
Никогда нельзя сохранять пароли в открытом виде, поэтому мы будем использовать (пока что ненастоящую) систему хеширования паролей.
Если пароли не совпадают, мы возвращаем ту же ошибку.
#### Хеширование паролей
#### Хеширование паролей { #password-hashing }
"Хеширование" означает: преобразование некоторого содержимого (в данном случае пароля) в последовательность байтов (просто строку), которая выглядит как тарабарщина.
@@ -113,19 +112,19 @@
Но преобразовать тарабарщину обратно в пароль невозможно.
##### Зачем использовать хеширование паролей
##### Зачем использовать хеширование паролей { #why-use-password-hashing }
Если ваша база данных будет украдена, то у вора не будет паролей пользователей в открытом виде, только хэши.
Если вашу базу данных украдут, у злоумышленника не будет паролей пользователей в открытом виде, только хэши.
Таким образом, вор не сможет использовать эти же пароли в другой системе (поскольку многие пользователи используют одни и те же пароли повсеместно, это было бы опасно).
Таким образом, он не сможет попробовать использовать эти же пароли в другой системе (поскольку многие пользователи используют один и тот же пароль повсеместно, это было бы опасно).
{* ../../docs_src/security/tutorial003_an_py310.py hl[82:85] *}
#### Про `**user_dict`
#### Про `**user_dict` { #about-user-dict }
`UserInDB(**user_dict)` означает:
*Передавать ключи и значения `user_dict` непосредственно в качестве аргументов ключ-значение, что эквивалентно:*
*Передать ключи и значения `user_dict` непосредственно как аргументы ключ-значение, эквивалентно:*
```Python
UserInDB(
@@ -138,23 +137,23 @@ UserInDB(
```
/// info | Дополнительная информация
Более полное объяснение `**user_dict` можно найти в [документации к **Дополнительным моделям**](../extra-models.md#about-user_indict){.internal-link target=_blank}.
Более полное объяснение `**user_dict` можно найти в [документации к **Дополнительным моделям**](../extra-models.md#about-user-in-dict){.internal-link target=_blank}.
///
## Возврат токена
## Возврат токена { #return-the-token }
Ответ эндпоинта `token` должен представлять собой объект в формате JSON.
Ответ операции пути `/token` должен быть объектом JSON.
Он должен иметь `token_type`. В нашем случае, поскольку мы используем токены типа "Bearer", тип токена должен быть "`bearer`".
В нём должен быть `token_type`. В нашем случае, поскольку мы используем токены типа "Bearer", тип токена должен быть `bearer`.
И в нем должна быть строка `access_token`, содержащая наш токен доступа.
И в нём должен быть `access_token` — строка, содержащая наш токен доступа.
В этом простом примере мы нарушим все правила безопасности, и будем считать, что имя пользователя (username) полностью соответствует токену (token)
В этом простом примере мы намеренно поступим небезопасно и вернём тот же `username` в качестве токена.
/// tip | Подсказка
В следующей главе мы рассмотрим реальную защищенную реализацию с хешированием паролей и токенами <abbr title="JSON Web Tokens">JWT</abbr>.
В следующей главе вы увидите реальную защищённую реализацию с хешированием паролей и токенами <abbr title="JSON Web Tokens JSON веб-токены">JWT</abbr>.
Но пока давайте остановимся на необходимых нам деталях.
Но пока давайте сосредоточимся на необходимых нам деталях.
///
{* ../../docs_src/security/tutorial003_an_py310.py hl[87] *}
@@ -162,50 +161,50 @@ UserInDB(
/// tip | Подсказка
Согласно спецификации, вы должны возвращать JSON с `access_token` и `token_type`, как в данном примере.
Это то, что вы должны сделать сами в своем коде и убедиться, что вы используете эти JSON-ключи.
Это то, что вы должны сделать сами в своём коде и убедиться, что вы используете именно эти JSON-ключи.
Это практически единственное, что нужно не забывать делать самостоятельно, чтобы следовать требованиям спецификации.
Это практически единственное, о чём нужно не забыть, чтобы соответствовать спецификациям.
Все остальное за вас сделает **FastAPI**.
Остальное за вас сделает **FastAPI**.
///
## Обновление зависимостей
## Обновление зависимостей { #update-the-dependencies }
Теперь мы обновим наши зависимости.
Мы хотим получить значение `current_user` *только* если этот пользователь активен.
Мы хотим получить `current_user` только если этот пользователь активен.
Поэтому мы создаем дополнительную зависимость `get_current_active_user`, которая, в свою очередь, использует в качестве зависимости `get_current_user`.
Поэтому мы создаём дополнительную зависимость `get_current_active_user`, которая, в свою очередь, использует в качестве зависимости `get_current_user`.
Обе эти зависимости просто вернут HTTP-ошибку, если пользователь не существует или неактивен.
Таким образом, в нашем эндпоинте мы получим пользователя только в том случае, если он существует, правильно аутентифицирован и активен:
Таким образом, в нашей операции пути мы получим пользователя только в том случае, если он существует, корректно аутентифицирован и активен:
{* ../../docs_src/security/tutorial003_an_py310.py hl[58:66,69:74,94] *}
/// info | Дополнительная информация
Дополнительный заголовок `WWW-Authenticate` со значением `Bearer`, который мы здесь возвращаем, также является частью спецификации.
Дополнительный HTTP-заголовок `WWW-Authenticate` со значением `Bearer`, который мы здесь возвращаем, также является частью спецификации.
Ответ сервера с HTTP-кодом 401 "UNAUTHORIZED" должен также возвращать заголовок `WWW-Authenticate`.
Любой HTTP статус-код 401 "UNAUTHORIZED" должен также возвращать заголовок `WWW-Authenticate`.
В случае с bearer-токенами (наш случай) значение этого заголовка должно быть `Bearer`.
На самом деле этот дополнительный заголовок можно пропустить и все будет работать.
Фактически, этот дополнительный заголовок можно опустить, и всё будет работать.
Но он приведён здесь для соответствия спецификации.
Но он приведён здесь для соответствия спецификациям.
Кроме того, могут существовать инструменты, которые ожидают его и могут использовать, и это может быть полезно для вас или ваших пользователей сейчас или в будущем.
Кроме того, могут существовать инструменты, которые ожидают его и могут использовать, и это может быть полезно для вас или ваших пользователей сейчас или в будущем.
В этом и заключается преимущество стандартов...
///
## Посмотим как это работает
## Посмотрим, как это работает { #see-it-in-action }
Откроем интерактивную документацию: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
Откройте интерактивную документацию: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
### Аутентификация
### Аутентификация { #authenticate }
Нажмите кнопку "Авторизация".
Нажмите кнопку "Authorize".
Используйте учётные данные:
@@ -215,13 +214,15 @@ UserInDB(
<img src="/img/tutorial/security/image04.png">
После авторизации в системе вы увидите следующее:
После аутентификации вы увидите следующее:
<img src="/img/tutorial/security/image05.png">
### Получение собственных пользовательских данных
### Получение собственных пользовательских данных { #get-your-own-user-data }
Теперь, используя операцию `GET` с путем `/users/me`, вы получите данные пользователя, например:
Теперь используйте операцию `GET` с путём `/users/me`.
Вы получите свои пользовательские данные, например:
```JSON
{
@@ -235,7 +236,7 @@ UserInDB(
<img src="/img/tutorial/security/image06.png">
Если щелкнуть на значке замка и выйти из системы, а затем попытаться выполнить ту же операцию ещё раз, то будет выдана ошибка HTTP 401:
Если щёлкнуть на значке замка и выйти из системы, а затем попытаться выполнить ту же операцию ещё раз, будет выдана ошибка HTTP 401:
```JSON
{
@@ -243,17 +244,17 @@ UserInDB(
}
```
### Неактивный пользователь
### Неактивный пользователь { #inactive-user }
Теперь попробуйте пройти аутентификацию с неактивным пользователем:
Теперь попробуйте с неактивным пользователем, аутентифицируйтесь с:
Пользователь: `alice`
Пароль: `secret2`
И попробуйте использовать операцию `GET` с путем `/users/me`.
И попробуйте использовать операцию `GET` с путём `/users/me`.
Вы получите ошибку "Inactive user", как тут:
Вы получите ошибку "Inactive user", как здесь:
```JSON
{
@@ -261,12 +262,12 @@ UserInDB(
}
```
## Резюме
## Резюме { #recap }
Теперь у вас есть инструменты для реализации полноценной системы безопасности на основе `имени пользователя` и `пароля` для вашего API.
Теперь у вас есть инструменты для реализации полноценной системы безопасности на основе `username` и `password` для вашего API.
Используя эти средства, можно сделать систему безопасности совместимой с любой базой данных, с любым пользователем или моделью данных.
Используя эти средства, можно сделать систему безопасности совместимой с любой базой данных и с любой пользовательской или моделью данных.
Единственным недостатком нашей системы является то, что она всё ещё не защищена.
Единственная деталь, которой не хватает, — система пока ещё не "защищена" по-настоящему.
В следующей главе вы увидите, как использовать библиотеку безопасного хеширования паролей и токены <abbr title="JSON Web Tokens">JWT</abbr>.
В следующей главе вы увидите, как использовать библиотеку безопасного хеширования паролей и токены <abbr title="JSON Web Tokens JSON веб-токены">JWT</abbr>.

View File

@@ -1,18 +1,18 @@
# SQL (реляционные) базы данных
# SQL (реляционные) базы данных { #sql-relational-databases }
**FastAPI** не требует использования реляционной базы данных. Вы можете воспользоваться любой базой данных, которой хотите.
**FastAPI** не требует использовать SQL (реляционную) базу данных. Но вы можете использовать любую базу данных, которую хотите.
В этом разделе мы продемонстрируем, как работать с <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">SQLModel</a>.
Здесь мы рассмотрим пример с использованием <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">SQLModel</a>.
Библиотека **SQLModel** построена на основе <a href="https://www.sqlalchemy.org/" class="external-link" target="_blank">SQLAlchemy</a> и Pydantic. Она была разработана автором **FastAPI** специально для приложений на основе FastAPI, которые используют **реляционные базы данных**.
**SQLModel** построен поверх <a href="https://www.sqlalchemy.org/" class="external-link" target="_blank">SQLAlchemy</a> и Pydantic. Его создал тот же автор, что и **FastAPI**, чтобы он идеально подходил для приложений FastAPI, которым нужны **SQL базы данных**.
/// tip | Подсказка
Вы можете воспользоваться любой библиотекой для работы с реляционными (SQL) или нереляционными (NoSQL) базами данных. (Их ещё называют <abbr title="ORM = Object Relational Mapper, этот термин для библиотеки, в которой классы представляют SQL-таблицы, а экземпляры классов представляют строки в этих таблицах.">**ORM**</abbr> библиотеками). FastAPI не принуждает вас к использованию чего-либо конкретного. 😎
Вы можете использовать любую другую библиотеку для работы с SQL или NoSQL базами данных (иногда их называют <abbr title="Object Relational Mapper Объектно-реляционный маппер: термин для библиотеки, где некоторые классы представляют SQL-таблицы, а экземпляры представляют строки в этих таблицах">"ORMs"</abbr>), FastAPI ничего не навязывает. 😎
///
В основе SQLModel лежит SQLAlchemy, поэтому вы спокойно можете использовать любую базу данных, поддерживаемую SQLAlchemy (и, соответственно, поддерживаемую SQLModel), например:
Так как SQLModel основан на SQLAlchemy, вы можете легко использовать **любую поддерживаемую** SQLAlchemy базу данных (а значит, и поддерживаемую SQLModel), например:
* PostgreSQL
* MySQL
@@ -20,21 +20,21 @@
* Oracle
* Microsoft SQL Server, и т.д.
В данном примере мы будем использовать базу данных **SQLite**, т.к. она состоит из единственного файла и поддерживается встроенными библиотеками Python. Таким образом, вы сможете скопировать данный пример и запустить его как он есть.
В этом примере мы будем использовать **SQLite**, потому что она использует один файл и имеет встроенную поддержку в Python. Так что вы можете скопировать этот пример и запустить его как есть.
В дальнейшем, для продакшн-версии вашего приложения, возможно, вам стоит использовать серверную базу данных, например, **PostgreSQL**.
Позже, для продакшн-приложения, возможно, вы захотите использовать серверную базу данных, например **PostgreSQL**.
/// tip | Подсказка
Существует официальный генератор проектов на **FastAPI** и **PostgreSQL**, который также включает frontend и дополнительные инструменты <a href="https://github.com/fastapi/full-stack-fastapi-template" class="external-link" target="_blank">https://github.com/fastapi/full-stack-fastapi-template</a>
Существует официальный генератор проектов на **FastAPI** и **PostgreSQL**, включающий frontend и другие инструменты: <a href="https://github.com/fastapi/full-stack-fastapi-template" class="external-link" target="_blank">https://github.com/fastapi/full-stack-fastapi-template</a>
///
Это очень простое и короткое руководство, поэтому, если вы хотите узнать о базах данных в целом, об SQL, разобраться с более продвинутым функционалом, то воспользуйтесь <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">документацией SQLModel</a>.
Это очень простое и короткое руководство. Если вы хотите узнать больше о базах данных в целом, об SQL или о более продвинутых возможностях, обратитесь к <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">документации SQLModel</a>.
## Установка `SQLModel`
## Установка `SQLModel` { #install-sqlmodel }
Создайте виртуальное окружение [virtual environment](../virtual-environments.md){.internal-link target=_blank}, активируйте его и установите `sqlmodel`:
Сначала убедитесь, что вы создали [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активировали его и затем установили `sqlmodel`:
<div class="termy">
@@ -45,110 +45,109 @@ $ pip install sqlmodel
</div>
## Создание приложения с единственной моделью
## Создание приложения с единственной моделью { #create-the-app-with-a-single-model }
Мы начнем с создания наиболее простой первой версии нашего приложения с одной единственной моделью **SQLModel**.
Сначала мы создадим самую простую первую версию приложения с одной моделью **SQLModel**.
В дальнейшем с помощью **дополнительных моделей** мы его улучшим и сделаем более безопасным и универсальным. 🤓
Позже мы улучшим его, повысив безопасность и универсальность, добавив **несколько моделей**. 🤓
### Создание моделей
### Создание моделей { #create-models }
Импортируйте `SQLModel` и создайте модель базы данных:
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[1:11] hl[7:11] *}
Класс `Hero` очень напоминает модель Pydantic (фактически, под капотом, *это и есть модель Pydantic*).
Класс `Hero` очень похож на модель Pydantic (фактически, под капотом, *это и есть модель Pydantic*).
Но есть и некоторые различия
Есть несколько отличий:
* `table=True` для SQLModel означает, что это *модель-таблица*, которая должна представлять **таблицу** в реляционной базе данных. Это не просто *модель данных* (в отличие от обычного класса в Pydantic).
* `table=True` сообщает SQLModel, что это *модель-таблица*, она должна представлять **таблицу** в SQL базе данных, это не просто *модель данных* (как обычный класс Pydantic).
* `Field(primary_key=True)` для SQLModel означает, что поле `id` является первичным ключом в таблице базы данных (вы можете подробнее узнать о первичных ключах баз данных в документации по SQLModel).
* `Field(primary_key=True)` сообщает SQLModel, что `id` — это **первичный ключ** в SQL базе данных (подробнее о первичных ключах можно узнать в документации SQLModel).
Тип `int | None` сигнализирует для SQLModel, что столбец таблицы базы данных должен иметь тип `INTEGER`, или иметь пустое значение `NULL`.
Благодаря типу `int | None`, SQLModel будет знать, что этот столбец должен быть `INTEGER` в SQL базе данных и должен допускать значение `NULL`.
* `Field(index=True)` для SQLModel означает, что нужно создать **SQL индекс** для данного столбца. Это обеспечит более быстрый поиск при чтении данных, отфильтрованных по данному столбцу.
* `Field(index=True)` сообщает SQLModel, что нужно создать **SQL индекс** для этого столбца, что позволит быстрее выполнять выборки при чтении данных, отфильтрованных по этому столбцу.
SQLModel будет знать, что данные типа `str`, будут представлены в базе данных как `TEXT` (или `VARCHAR`, в зависимости от типа базы данных).
SQLModel будет знать, что объявленное как `str` станет SQL-столбцом типа `TEXT` (или `VARCHAR`, в зависимости от базы данных).
### Создание соединения с базой данных (Engine)
### Создание Engine { #create-an-engine }
В SQLModel объект соединения `engine` (по сути это `Engine` из SQLAlchemy) **содержит пул соединений** к базе данных.
Объект `engine` в SQLModel (под капотом это `engine` из SQLAlchemy) **удерживает соединения** с базой данных.
Для обеспечения всех подключений приложения к одной базе данных нужен только один объект соединения `engine`.
У вас должен быть **один объект `engine`** для всей кодовой базы, чтобы подключаться к одной и той же базе данных.
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[14:18] hl[14:15,17:18] *}
Использование настройки `check_same_thread=False` позволяет FastAPI использовать одну и ту же SQLite базу данных в различных потоках (threads). Это необходимо, когда **один запрос** использует **более одного потока** (например, в зависимостях).
Параметр `check_same_thread=False` позволяет FastAPI использовать одну и ту же базу данных SQLite в разных потоках. Это необходимо, так как **один запрос** может использовать **больше одного потока** (например, в зависимостях).
Не беспокойтесь, учитывая структуру кода, мы позже позаботимся о том, чтобы использовать **отдельную SQLModel-сессию на каждый отдельный запрос**, это как раз то, что пытается обеспечить `check_same_thread`.
Не волнуйтесь, с такой структурой кода мы позже обеспечим использование **одной *сессии* SQLModel на запрос**, по сути именно этого и добивается `check_same_thread`.
### Создание таблиц
### Создание таблиц { #create-the-tables }
Далее мы добавляем функцию, использующую `SQLModel.metadata.create_all(engine)`, для того, чтобы создать **таблицы** для каждой из **моделей таблицы**.
Далее мы добавим функцию, которая использует `SQLModel.metadata.create_all(engine)`, чтобы **создать таблицы** для всех *моделей-таблиц*.
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[21:22] hl[21:22] *}
### Создание зависимости Session
### Создание зависимости Session { #create-a-session-dependency }
Сессия базы данных (**`Session`**) хранит **объекты в памяти** и отслеживает любые необходимые изменения в данных, а затем **использует `engine`** для коммуникации с базой данных.
**`Session`** хранит **объекты в памяти** и отслеживает необходимые изменения в данных, затем **использует `engine`** для общения с базой данных.
Мы создадим FastAPI-**зависимость** с помощью `yield`, которая будет создавать новую сессию (Session) для каждого запроса. Это как раз и обеспечит использование отдельной сессии на каждый отдельный запрос. 🤓
Мы создадим **зависимость** FastAPI с `yield`, которая будет предоставлять новую `Session` для каждого запроса. Это и обеспечивает использование одной сессии на запрос. 🤓
Затем мы создадим объявленную (`Annotated`) зависимость `SessionDep`. Мы сделаем это для того, чтобы упростить остальной код, который будет использовать эту зависимость.
Затем мы создадим объявленную (`Annotated`) зависимость `SessionDep`, чтобы упростить остальной код, который будет использовать эту зависимость.
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[25:30] hl[25:27,30] *}
### Создание таблиц базы данных при запуске приложения
### Создание таблиц базы данных при старте { #create-database-tables-on-startup }
Мы будем создавать таблицы базы данных при запуске приложения.
Мы создадим таблицы базы данных при запуске приложения.
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[32:37] hl[35:37] *}
В данном примере мы создаем таблицы при наступлении события запуска приложения.
Здесь мы создаём таблицы в обработчике события запуска приложения.
В продуктовом приложении вы, скорее всего, будете использовать скрипт для миграции базы данных, который выполняется перед запуском приложения. 🤓
Для продакшна вы, вероятно, будете использовать скрипт миграций, который выполняется до запуска приложения. 🤓
/// tip | Подсказка
В SQLModel будут включены утилиты миграции, входящие в состав Alembic, но на данный момент вы просто можете использовать
<a href="https://alembic.sqlalchemy.org/en/latest/" class="external-link" target="_blank">Alembic</a> напрямую.
В SQLModel появятся утилиты миграций - обёртки над Alembic, но пока вы можете использовать <a href="https://alembic.sqlalchemy.org/en/latest/" class="external-link" target="_blank">Alembic</a> напрямую.
///
### Создание героя (Hero)
### Создание героя (Hero) { #create-a-hero }
Каждая модель в SQLModel является также моделью Pydantic, поэтому вы можете использовать её при **объявлении типов**, точно также, как и модели Pydantic.
Так как каждая модель SQLModel также является моделью Pydantic, вы можете использовать её в тех же **аннотациях типов**, в которых используете модели Pydantic.
Например, при объявлении параметра типа `Hero`, она будет считана из **тела JSON**.
Например, если вы объявите параметр типа `Hero`, он будет прочитан из **JSON body (тела запроса)**.
Точно также, вы можете использовать её при объявлении типа значения, возвращаемого функцией, и тогда структурированные данные будут отображены через пользовательский интерфейс автоматически сгенерированной документации FastAPI.
Аналогично вы можете объявить её как **тип возвращаемого значения** функции, и тогда форма данных отобразится в автоматически сгенерированном UI документации API.
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[40:45] hl[40:45] *}
Мы используем зависимость `SessionDep` (сессию базы данных) для того, чтобы добавить нового героя `Hero` в объект сессии (`Session`), сохранить изменения в базе данных, обновить данные героя и затем вернуть их.
Здесь мы используем зависимость `SessionDep` (это `Session`), чтобы добавить нового `Hero` в экземпляр `Session`, зафиксировать изменения в базе данных, обновить данные в `hero` и затем вернуть его.
### Чтение данных о героях
### Чтение героев { #read-heroes }
Мы можем **читать** данные героев из базы данных с помощью `select()`. Мы можем включить `limit` и `offset` для постраничного считывания результатов.
Мы можем **читать** записи `Hero` из базы данных с помощью `select()`. Можно добавить `limit` и `offset` для постраничного вывода результатов.
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[48:55] hl[51:52,54] *}
### Чтение данных отдельного героя
### Чтение одного героя { #read-one-hero }
Мы можем прочитать данные отдельного героя (`Hero`).
Мы можем **прочитать** одного `Hero`.
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[58:63] hl[60] *}
### Удаление данных героя
### Удаление героя { #delete-a-hero }
Мы также можем удалить героя `Hero` из базы данных.
Мы также можем **удалить** `Hero`.
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[66:73] hl[71] *}
### Запуск приложения
### Запуск приложения { #run-the-app }
Вы можете запустить приложение следующим образом:
Вы можете запустить приложение:
<div class="termy">
@@ -160,49 +159,49 @@ $ fastapi dev main.py
</div>
Далее перейдите в пользовательский интерфейс API `/docs`. Вы увидите, что **FastAPI** использует модели для создания документации API. Эти же модели используются для сериализации и проверки данных.
Затем перейдите в UI `/docs`. Вы увидите, что **FastAPI** использует эти **модели** для **документирования** API, а также для **сериализации** и **валидации** данных.
<div class="screenshot">
<img src="/img/tutorial/sql-databases/image01.png">
</div>
## Добавление в приложение дополнительных (вспомогательных) моделей
## Обновление приложения с несколькими моделями { #update-the-app-with-multiple-models }
Теперь давайте проведём **рефакторинг** нашего приложения, чтобы сделать его более безопасным и более универсальным.
Теперь давайте немного **отрефакторим** приложение, чтобы повысить **безопасность** и **универсальность**.
Обратите внимание, что на данном этапе наше приложение позволяет на уровне клиента определять `id` создаваемого героя (`Hero`). 😱
Если вы посмотрите на предыдущую версию, в UI видно, что до сих пор клиент мог сам задавать `id` создаваемого `Hero`. 😱
Мы не можем этого допустить, т.к. существует риск переписать уже присвоенные `id` в базе данных. Присвоение `id` должно происходить **на уровне бэкэнда (backend)** или **на уровне базы данных**, но никак **не на уровне клиента**.
Так делать нельзя, иначе они могли бы перезаписать `id`, который уже присвоен в БД. Решение по `id` должно приниматься **бэкендом** или **базой данных**, а **не клиентом**.
Кроме того, мы создаем секретное имя `secret_name` для героя, но пока что, мы возвращаем его повсеместно, и это слабо напоминает **секретность**... 😅
Кроме того, мы создаём для героя `secret_name`, но пока что возвращаем его повсюду — это не очень **секретно**... 😅
Мы поправим это с помощью нескольких дополнительных (вспомогательных) моделей. Вот где SQLModel по-настоящему покажет себя. ✨
Мы исправим это, добавив несколько **дополнительных моделей**. Здесь SQLModel раскроется во всей красе. ✨
### Создание дополнительных моделей
### Создание нескольких моделей { #create-multiple-models }
В **SQLModel**, любая модель с параметром `table=True` является **моделью таблицы**.
В **SQLModel** любая модель с `table=True` — это **модель-таблица**.
Любая модель, не содержащая `table=True` является **моделью данных**, это по сути обычные модели Pydantic (с несколько расширенным функционалом). 🤓
Любая модель без `table=True` — это **модель данных**, по сути обычная модель Pydantic (с парой небольших дополнений). 🤓
С помощью SQLModel мы можем использовать **наследование**, что поможет нам **избежать дублирования** всех полей.
С SQLModel мы можем использовать **наследование**, чтобы **избежать дублирования** полей.
#### Базовый класс `HeroBase`
#### `HeroBase` — базовый класс { #herobase-the-base-class }
Давайте начнём с модели `HeroBase`, которая содержит поля, общие для всех моделей:
Начнём с модели `HeroBase`, которая содержит **общие поля** для всех моделей:
* `name`
* `age`
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:9] hl[7:9] *}
#### Модель таблицы `Hero`
#### `Hero` — *модель-таблица* { #hero-the-table-model }
Далее давайте создадим **модель таблицы** `Hero` с дополнительными полями, которых может не быть в других моделях:
Далее создадим `Hero`, фактическую *модель-таблицу*, с **дополнительными полями**, которых может не быть в других моделях:
* `id`
* `secret_name`
Модель `Hero` наследует от `HeroBase`, и поэтому включает также поля из `HeroBase`. Таким образом, все поля, содержащиеся в `Hero`, будут следующими:
Так как `Hero` наследуется от `HeroBase`, он **также** имеет **поля**, объявленные в `HeroBase`, поэтому все поля `Hero`:
* `id`
* `name`
@@ -211,25 +210,25 @@ $ fastapi dev main.py
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:14] hl[12:14] *}
#### Публичная модель данных `HeroPublic`
#### `HeroPublic` — публичная *модель данных* { #heropublic-the-public-data-model }
Далее мы создадим модель `HeroPublic`. Мы будем возвращать её клиентам API.
Далее мы создадим модель `HeroPublic`, именно она будет **возвращаться** клиентам API.
Она включает в себя те же поля, что и `HeroBase`, и, соответственно, поле `secret_name` в ней отсутствует.
У неё те же поля, что и у `HeroBase`, поэтому она не включает `secret_name`.
Наконец-то личность наших героев защищена! 🥷
В модели `HeroPublic` также объявляется поле `id: int`. Мы как бы заключаем договоренность с API клиентом, на то, что передаваемые данные всегда должны содержать поле `id`, и это поле должно содержать целое число (и никогда не содержать `None`).
Также здесь заново объявляется `id: int`. Тем самым мы заключаем **контракт** с клиентами API: они всегда могут рассчитывать, что поле `id` присутствует и это `int` (никогда не `None`).
/// tip | Подсказка
Модель ответа, гарантирующая наличие поля со значением типа `int` (не `None`), очень полезна при разработке API клиентов. Определенность в передаваемых данных может обеспечить написание более простого кода.
Гарантия того, что в модели ответа значение всегда присутствует и это `int` (не `None`), очень полезна для клиентов API — так можно писать гораздо более простой код.
Также **автоматически генерируемые клиенты** будут иметь более простой интерфейс. И в результате жизнь разработчиков, использующих ваш API, станет значительно легче. 😎
Кроме того, **автоматически сгенерированные клиенты** будут иметь более простые интерфейсы, и разработчикам, взаимодействующим с вашим API, будет работать значительно комфортнее. 😎
///
`HeroPublic` содержит все поля `HeroBase`, а также поле `id`, объявленное как `int` (не `None`):
Все поля `HeroPublic` такие же, как в `HeroBase`, а `id` объявлен как `int` (не `None`):
* `id`
* `name`
@@ -237,23 +236,23 @@ $ fastapi dev main.py
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:18] hl[17:18] *}
#### Модель для создания героя `HeroCreate`
#### `HeroCreate` — *модель данных* для создания героя { #herocreate-the-data-model-to-create-a-hero }
Сейчас мы создадим модель `HeroCreate`. Эта модель будет использоваться для проверки данных, переданных клиентом.
Теперь создадим модель `HeroCreate`, она будет **валидировать** данные от клиентов.
Она содержит те же поля, что и `HeroBase`, а также поле `secret_name`.
У неё те же поля, что и у `HeroBase`, а также есть `secret_name`.
Теперь, при создании нового героя, клиенты будут передавать секретное имя `secret_name`, которое будет сохранено в базе данных, но не будет возвращено в ответе API клиентам.
Теперь, когда клиенты **создают нового героя**, они будут отправлять `secret_name`, он сохранится в базе данных, но не будет возвращаться клиентам в API.
/// tip | Подсказка
Вот как нужно работать с **паролями**: получайте их, но не возвращайте их через API.
Так следует обрабатывать **пароли**: принимать их, но не возвращать в API.
Также хэшируйте значения паролей перед тем, как их сохранить. Ни в коем случае не храните пароли в открытом виде, как обычный текст.
Также перед сохранением значения паролей нужно **хэшировать**, **никогда не храните их в открытом виде**.
///
Поля модели `HeroCreate`:
Поля `HeroCreate`:
* `name`
* `age`
@@ -261,15 +260,15 @@ $ fastapi dev main.py
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:22] hl[21:22] *}
#### Модель для обновления данных героя `HeroUpdate`
#### `HeroUpdate` — *модель данных* для обновления героя { #heroupdate-the-data-model-to-update-a-hero }
В предыдущих версиях нашей программы мы не могли обновить данные героя, теперь, воспользовавшись дополнительными моделями, мы сможем это сделать. 🎉
В предыдущей версии приложения у нас не было способа **обновлять героя**, но теперь, с **несколькими моделями**, мы можем это сделать. 🎉
Модель данных `HeroUpdate` в некотором смысле особенная. Она содержит все те же поля, что и модель создания героя, но все поля модели являются **необязательными**. (Все они имеют значение по умолчанию.) Таким образом, при обновлении данных героя, вам достаточно передать только те поля, которые требуют изменения.
*Модель данных* `HeroUpdate` особенная: у неё **те же поля**, что и для создания нового героя, но все поля **необязательные** (у всех есть значение по умолчанию). Таким образом, при обновлении героя можно отправлять только те поля, которые нужно изменить.
Поскольку **все поля по сути меняются** (теперь тип каждого поля допускает значение `None` и значение по умолчанию `None`), мы должны их **объявить заново**.
Поскольку **фактически меняются все поля** (их тип теперь включает `None`, и по умолчанию они равны `None`), нам нужно **переобъявить** их.
Фактически, нам не нужно наследоваться от `HeroBase`, потому что мы будем заново объявлять все поля. Я оставлю наследование просто для поддержания общего стиля, но оно (наследование) здесь необязательно. 🤷
Наследоваться от `HeroBase` не обязательно, так как мы заново объявляем все поля. Я оставлю наследование для единообразия, но это не необходимо. Скорее дело вкуса. 🤷
Поля `HeroUpdate`:
@@ -279,59 +278,59 @@ $ fastapi dev main.py
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:28] hl[25:28] *}
### Создание героя с помощью `HeroCreate` и возвращение результатов с помощью `HeroPublic`
### Создание с `HeroCreate` и возврат `HeroPublic` { #create-with-herocreate-and-return-a-heropublic }
Теперь, когда у нас есть дополнительные модели, мы можем обновить те части приложения, которые их используют.
Теперь, когда у нас есть **несколько моделей**, мы можем обновить части приложения, которые их используют.
Вместе c запросом на создание героя мы получаем объект данных `HeroCreate`, и создаем на его основе объект модели таблицы `Hero`.
Мы получаем в запросе *модель данных* `HeroCreate` и на её основе создаём *модель-таблицу* `Hero`.
Созданный объект *модели таблицы* `Hero` будет иметь все поля, переданные клиентом, а также поле `id`, сгенерированное базой данных.
Новая *модель-таблица* `Hero` будет иметь поля, отправленные клиентом, а также `id`, сгенерированный базой данных.
Далее функция вернёт объект *модели таблицы* `Hero`. Но поскольку, мы объявили `HeroPublic` как модель ответа, то **FastAPI** будет использовать именно её для проверки и сериализации данных.
Затем возвращаем из функции ту же *модель-таблицу* `Hero` как есть. Но так как мы объявили `response_model` с *моделью данных* `HeroPublic`, **FastAPI** использует `HeroPublic` для валидации и сериализации данных.
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[56:62] hl[56:58] *}
/// tip | Подсказка
Теперь мы используем модель ответа `response_model=HeroPublic`, вместо того, чтобы объявить тип возвращаемого значения как `-> HeroPublic`. Мы это делаем потому, что тип возвращаемого значения не относится к `HeroPublic`.
Теперь мы используем `response_model=HeroPublic` вместо **аннотации типа возвращаемого значения** `-> HeroPublic`, потому что фактически возвращаемое значение — это *не* `HeroPublic`.
Если бы мы объявили тип возвращаемого значения как `-> HeroPublic`, то редактор и линтер начали бы ругаться (и вполне справедливо), что возвращаемое значение принадлежит типу `Hero`, а совсем не `HeroPublic`.
Если бы мы объявили `-> HeroPublic`, ваш редактор кода и линтер справедливо пожаловались бы, что вы возвращаете `Hero`, а не `HeroPublic`.
Объявляя модель ответа в `response_model`, мы как бы говорим **FastAPI**: делай свое дело, не вмешиваясь в аннотацию типов и не полагаясь на помощь редактора или других инструментов.
Объявляя модель в `response_model`, мы говорим **FastAPI** сделать своё дело, не вмешиваясь в аннотации типов и работу редактора кода и других инструментов.
///
### Чтение данных героев с помощью `HeroPublic`
### Чтение героев с `HeroPublic` { #read-heroes-with-heropublic }
Мы можем проделать то же самое **для чтения данных** героев. Мы применим модель ответа `response_model=list[HeroPublic]`, и тем самым обеспечим правильную проверку и сериализацию данных.
Аналогично мы можем **читать** `Hero` — снова используем `response_model=list[HeroPublic]`, чтобы данные валидировались и сериализовались корректно.
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[65:72] hl[65] *}
### Чтение данных отдельного героя с помощью `HeroPublic`
### Чтение одного героя с `HeroPublic` { #read-one-hero-with-heropublic }
Мы можем **прочитать** данные отдельного героя:
Мы можем **прочитать** одного героя:
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[75:80] hl[77] *}
### Обновление данных героя с помощью `HeroUpdate`
### Обновление героя с `HeroUpdate` { #update-a-hero-with-heroupdate }
Мы можем **обновить данные героя**. Для этого мы воспользуемся HTTP методом `PATCH`.
Мы можем **обновить героя**. Для этого используем HTTP операцию `PATCH`.
В коде мы получаем объект словаря `dict` с данными, переданными клиентом (т.е. **только c данными, переданными клиентом**, исключая любые значения, которые могли бы быть там только потому, что они являются значениями по умолчанию). Для того чтобы сделать это, мы воспользуемся опцией `exclude_unset=True`. В этом главная хитрость. 🪄
В коде мы получаем `dict` со всеми данными, отправленными клиентом **только с данными, отправленными клиентом**, исключая любые значения, которые были бы там лишь как значения по умолчанию. Для этого мы используем `exclude_unset=True`. Это главный трюк. 🪄
Затем мы применим `hero_db.sqlmodel_update(hero_data)`, и обновим `hero_db`, использовав данные `hero_data`.
Затем мы используем `hero_db.sqlmodel_update(hero_data)`, чтобы обновить `hero_db` данными из `hero_data`.
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[83:93] hl[83:84,88:89] *}
### Удалим героя ещё раз
### Снова удаление героя { #delete-a-hero-again }
Операция **удаления** героя практически не меняется.
Операция **удаления** героя остаётся практически прежней.
В данном случае желание *`отрефакторить всё`* остаётся неудовлетворенным. 😅
Желание *«отрефакторить всё»* на этот раз останется неудовлетворённым. 😅
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[96:103] hl[101] *}
### Снова запустим приложение
### Снова запустим приложение { #run-the-app-again }
Вы можете снова запустить приложение:
@@ -345,14 +344,14 @@ $ fastapi dev main.py
</div>
Если вы перейдете в пользовательский интерфейс API `/docs`, то вы увидите, что он был обновлен, и больше не принимает параметра `id` от клиента при создании нового героя, и т.д.
Если вы перейдёте в UI API `/docs`, вы увидите, что он обновился: теперь при создании героя он не ожидает получить `id` от клиента и т. д.
<div class="screenshot">
<img src="/img/tutorial/sql-databases/image02.png">
</div>
## Резюме
## Резюме { #recap }
Вы можете использовать <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">**SQLModel**</a> для взаимодействия с реляционными базами данных, а также для упрощения работы с **моделями данных** и **моделями таблиц**.
Вы можете использовать <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">**SQLModel**</a> для взаимодействия с SQL базой данных и упростить код с помощью *моделей данных* и *моделей-таблиц*.
Вы можете узнать гораздо больше информации в документации по **SQLModel**. Там вы найдете более подробное <a href="https://sqlmodel.tiangolo.com/tutorial/fastapi/" class="external-link" target="_blank">мини-руководство по использованию SQLModel с **FastAPI**</a>. 🚀
Гораздо больше вы можете узнать в документации **SQLModel**, там есть более подробный мини-<a href="https://sqlmodel.tiangolo.com/tutorial/fastapi/" class="external-link" target="_blank">туториал по использованию SQLModel с **FastAPI**</a>. 🚀

View File

@@ -1,11 +1,11 @@
# Статические Файлы
# Статические Файлы { #static-files }
Вы можете предоставлять статические файлы автоматически из директории, используя `StaticFiles`.
## Использование `StaticFiles`
## Использование `StaticFiles` { #use-staticfiles }
* Импортируйте `StaticFiles`.
* "Примонтируйте" экземпляр `StaticFiles()` с указанием определенной директории.
* "Примонтируйте" экземпляр `StaticFiles()` к определённому пути.
{* ../../docs_src/static_files/tutorial001.py hl[2,6] *}
@@ -17,16 +17,16 @@
///
### Что такое "Монтирование"
### Что такое "Монтирование" { #what-is-mounting }
"Монтирование" означает добавление полноценного "независимого" приложения в определенную директорию, которое затем обрабатывает все подпути.
"Монтирование" означает добавление полноценного "независимого" приложения на определённый путь, которое затем обрабатывает все подпути.
Это отличается от использования `APIRouter`, так как примонтированное приложение является полностью независимым.
OpenAPI и документация из вашего главного приложения не будет содержать ничего из примонтированного приложения, и т.д.
OpenAPI и документация из вашего главного приложения не будут содержать ничего из примонтированного приложения, и т.д.
Вы можете прочитать больше об этом в **Расширенном руководстве пользователя**.
Вы можете прочитать больше об этом в [Расширенном руководстве пользователя](../advanced/index.md){.internal-link target=_blank}.
## Детали
## Детали { #details }
Первый параметр `"/static"` относится к подпути, по которому это "подприложение" будет "примонтировано". Таким образом, любой путь начинающийся со `"/static"` будет обработан этим приложением.
@@ -36,6 +36,6 @@ OpenAPI и документация из вашего главного прил
Все эти параметры могут отличаться от "`static`", настройте их в соответствии с вашими нуждами и конкретными деталями вашего собственного приложения.
## Больше информации
## Больше информации { #more-info }
Для получения дополнительной информации о деталях и настройках ознакомьтесь с <a href="https://www.starlette.io/staticfiles/" class="external-link" target="_blank">Документацией Starlette о статических файлах</a>.

View File

@@ -1,4 +1,4 @@
# Тестирование
# Тестирование { #testing }
Благодаря <a href="https://www.starlette.io/testclient/" class="external-link" target="_blank">Starlette</a>, тестировать приложения **FastAPI** легко и приятно.
@@ -6,13 +6,17 @@
Используя эти инструменты, Вы можете напрямую задействовать <a href="https://docs.pytest.org/" class="external-link" target="_blank">pytest</a> с **FastAPI**.
## Использование класса `TestClient`
## Использование класса `TestClient` { #using-testclient }
/// info | Информация
Для использования класса `TestClient` необходимо установить библиотеку <a href="https://www.python-httpx.org" class="external-link" target="_blank">`httpx`</a>.
Для использования класса `TestClient` сначала установите <a href="https://www.python-httpx.org" class="external-link" target="_blank">`httpx`</a>.
Например, так: `pip install httpx`.
Убедитесь, что Вы создали [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активировали его, а затем установили пакет, например:
```console
$ pip install httpx
```
///
@@ -42,7 +46,7 @@
Также можно написать `from starlette.testclient import TestClient`.
**FastAPI** предоставляет тот же самый `starlette.testclient` как `fastapi.testclient`. Это всего лишь небольшое удобство для Вас, как разработчика.
**FastAPI** предоставляет тот же самый `starlette.testclient` как `fastapi.testclient`. Это всего лишь небольшое удобство для Вас, как разработчика. Но он берётся напрямую из Starlette.
///
@@ -52,13 +56,13 @@
///
## Разделение тестов и приложения
## Разделение тестов { #separating-tests }
В реальном приложении Вы, вероятно, разместите тесты в отдельном файле.
Кроме того, Ваше приложение **FastAPI** может состоять из нескольких файлов, модулей и т.п.
### Файл приложения **FastAPI**
### Файл приложения **FastAPI** { #fastapi-app-file }
Допустим, структура файлов Вашего приложения похожа на ту, что описана на странице [Более крупные приложения](bigger-applications.md){.internal-link target=_blank}:
@@ -69,12 +73,12 @@
│   └── main.py
```
Здесь файл `main.py` является "точкой входа" в Ваше приложение и содержит инициализацию Вашего приложения **FastAPI**:
В файле `main.py` находится Ваше приложение **FastAPI**:
{* ../../docs_src/app_testing/main.py *}
### Файл тестов
### Файл тестов { #testing-file }
Также у Вас может быть файл `test_main.py` содержащий тесты. Можно разместить тестовый файл и файл приложения в одной директории (в директориях для Python-кода желательно размещать и файл `__init__.py`):
@@ -93,11 +97,11 @@
...и писать дальше тесты, как и раньше.
## Тестирование: расширенный пример
## Тестирование: расширенный пример { #testing-extended-example }
Теперь давайте расширим наш пример и добавим деталей, чтоб посмотреть, как тестировать различные части приложения.
### Расширенный файл приложения **FastAPI**
### Расширенный файл приложения **FastAPI** { #extended-fastapi-app-file }
Мы продолжим работу с той же файловой структурой, что и ранее:
@@ -113,7 +117,7 @@
В нём описана операция `GET`, которая может вернуть ошибку.
Ещё есть операция `POST` и она тоже может вернуть ошибку.
Ещё есть операция `POST`, и она может вернуть несколько ошибок.
Обе *операции пути* требуют наличия в запросе заголовка `X-Token`.
@@ -155,7 +159,7 @@
////
//// tab | Python 3.8+ без Annotated
//// tab | Python 3.8+ без Annotated
/// tip | Подсказка
@@ -169,7 +173,7 @@
////
### Расширенный файл тестов
### Расширенный файл тестов { #extended-testing-file }
Теперь обновим файл `test_main.py`, добавив в него тестов:
@@ -198,9 +202,11 @@
///
## Запуск тестов
## Запуск { #run-it }
Далее Вам нужно установить `pytest`:
Далее Вам нужно установить `pytest`.
Убедитесь, что Вы создали [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активировали его, а затем установили пакет, например:
<div class="termy">
@@ -214,7 +220,7 @@ $ pip install pytest
Он автоматически найдёт все файлы и тесты, выполнит их и предоставит Вам отчёт о результатах тестирования.
Запустите тесты командой `pytest` и увидите результат:
Запустите тесты:
<div class="termy">