mirror of
https://github.com/fastapi/fastapi.git
synced 2026-05-18 13:27:45 -04:00
🌐 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:
@@ -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` с параметрами в функциях‑обработчиках пути и зависимостях, чтобы добавлять фоновые задачи.
|
||||
|
||||
@@ -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`:
|
||||
|
||||
|
||||
@@ -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, чтобы задавать дополнительную валидацию и метаданные для атрибутов модели.
|
||||
|
||||
|
||||
@@ -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** справится с этим, предоставит правильные данные в вашей функции, а также сделает валидацию и документацию правильной схемы *операции пути*.
|
||||
|
||||
|
||||
@@ -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, сохраняя при этом простоту, краткость и элегантность вашего кода.
|
||||
|
||||
И дополнительно вы получаете:
|
||||
|
||||
* Поддержку редактора (автодополнение доступно везде!)
|
||||
* Поддержку редактора кода (автозавершение доступно везде!)
|
||||
* Преобразование данных (также известно как парсинг / сериализация)
|
||||
* Валидацию данных
|
||||
* Документацию схемы данных
|
||||
|
||||
@@ -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`.
|
||||
* Сохранить данные в своей БД.
|
||||
* Вернуть обновленную модель.
|
||||
|
||||
|
||||
@@ -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}.
|
||||
|
||||
@@ -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**. 😎
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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 | Технические детали
|
||||
|
||||
|
||||
@@ -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 приложение) напрямую из отладчика.
|
||||
|
||||
|
||||
@@ -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** заботится о том, чтобы помочь вам свести к минимуму повторение кода.
|
||||
|
||||
|
||||
@@ -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` приложения, так чтобы они применялись к каждой *операции пути*.
|
||||
|
||||
@@ -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 сделает это за вас на внутреннем уровне.
|
||||
|
||||
|
||||
@@ -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` для целой группы *операций пути*.
|
||||
|
||||
@@ -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, чтобы это отображалось в системах интерактивной документации.
|
||||
|
||||
@@ -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 }
|
||||
|
||||
Помимо всех этих умных слов, используемых здесь, система внедрения зависимостей довольно проста.
|
||||
|
||||
|
||||
@@ -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** внутри системы для преобразования данных. Однако он полезен и во многих других сценариях.
|
||||
|
||||
|
||||
@@ -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] *}
|
||||
|
||||
@@ -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-моделей и свободно применяйте наследование для каждой из них.
|
||||
|
||||
|
||||
@@ -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">'/home/user/code/awesomeapp'</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>):
|
||||
|
||||

|
||||
|
||||
### Альтернативная документация 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>):
|
||||
|
||||

|
||||
|
||||
### 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`.
|
||||
|
||||
@@ -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`:
|
||||
|
||||
|
||||
@@ -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**. 😎
|
||||
|
||||
@@ -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`.
|
||||
|
||||
|
||||
@@ -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">'/home/user/code/awesomeapp'</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 }
|
||||
|
||||
Существует также **Продвинутое руководство пользователя**, которое вы сможете прочитать после руководства **Учебник - Руководство пользователя**.
|
||||
Существует также **Продвинутое руководство пользователя**, которое вы сможете прочитать после **Учебник - Руководство пользователя**.
|
||||
|
||||
**Продвинутое руководство пользователя** основано на этом, использует те же концепции и учит вас некоторым дополнительным функциям.
|
||||
**Продвинутое руководство пользователя** основано на этом, использует те же концепции и обучает некоторым дополнительным функциям.
|
||||
|
||||
Но вы должны сначала прочитать **Учебник - Руководство пользователя** (то, что вы читаете прямо сейчас).
|
||||
Но сначала вам следует прочитать **Учебник - Руководство пользователя** (то, что вы читаете прямо сейчас).
|
||||
|
||||
Он разработан таким образом, что вы можете создать полноценное приложение, используя только **Учебник - Руководство пользователя**, а затем расширить его различными способами, в зависимости от ваших потребностей, используя некоторые дополнительные идеи из **Продвинутого руководства пользователя**.
|
||||
Оно спроектировано так, что вы можете создать полноценное приложение, используя только **Учебник - Руководство пользователя**, а затем расширить его различными способами, в зависимости от ваших потребностей, используя дополнительные идеи из **Продвинутого руководства пользователя**.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 }
|
||||
|
||||
Вы можете легко конфигурировать и добавлять метаданные в ваши *операции пути*, передавая параметры *декораторам операций пути*.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
Все они используют те же параметры для дополнительной валидации и метаданных, которые вы видели ранее.
|
||||
|
||||
|
||||
@@ -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 и автоматическую документацию
|
||||
|
||||
И объявлять типы достаточно один раз.
|
||||
|
||||
|
||||
@@ -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-заголовки, но об этом вы прочитаете позже. 🤫
|
||||
|
||||
///
|
||||
|
||||
@@ -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`.
|
||||
|
||||
Смотрите следующие главы, чтобы узнать, как объявлять проверки для других типов, например чисел.
|
||||
|
||||
@@ -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}.
|
||||
|
||||
///
|
||||
|
||||
@@ -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` для работы с файлами, которые будут загружаться и передаваться в виде данных формы.
|
||||
|
||||
@@ -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. 😎
|
||||
|
||||
@@ -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` вместе, когда необходимо получить данные и файлы в одном запросе.
|
||||
|
||||
@@ -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` для объявления входных параметров данных формы.
|
||||
|
||||
@@ -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`, чтобы возвращать только те значения, которые были установлены явно.
|
||||
|
||||
@@ -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 статус-код, отличный от значения по умолчанию, которое вы объявляете здесь.
|
||||
|
||||
@@ -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 или выше** — так всё будет значительно **проще, последовательнее и интуитивнее**, и вам не придётся знать все эти исторические подробности. 😎
|
||||
|
||||
@@ -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 дополнительные строки вы получаете некую примитивную форму защиты.
|
||||
Таким образом, всего за 3–4 дополнительные строки у вас уже есть некая примитивная форма защиты.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
Это будет рассмотрено в следующем разделе.
|
||||
Это будет дальше.
|
||||
|
||||
@@ -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 от имени их пользователей.
|
||||
|
||||
@@ -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>.
|
||||
|
||||
@@ -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>. 🚀
|
||||
|
||||
@@ -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>.
|
||||
|
||||
@@ -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">
|
||||
|
||||
|
||||
Reference in New Issue
Block a user