Sync fastapi docs from b5ca1324 on 2025-12-07
Issue Manager / issue-manager (push) Has been cancelled
Build Docs / changes (push) Has been cancelled
Build Docs / langs (push) Has been cancelled
Build Docs / build-docs (push) Has been cancelled
Build Docs / docs-all-green (push) Has been cancelled
Conflict detector / main (push) Has been cancelled
Test Redistribute / test-redistribute (fastapi) (push) Has been cancelled
Test Redistribute / test-redistribute (fastapi-slim) (push) Has been cancelled
Test Redistribute / test-redistribute-alls-green (push) Has been cancelled
Test / lint (push) Has been cancelled
Test / test (pydantic-v1, 3.10) (push) Has been cancelled
Test / test (pydantic-v1, 3.11) (push) Has been cancelled
Test / test (pydantic-v1, 3.13) (push) Has been cancelled
Test / test (pydantic-v1, 3.8) (push) Has been cancelled
Test / test (pydantic-v1, 3.9) (push) Has been cancelled
Test / test (pydantic-v2, 3.10) (push) Has been cancelled
Test / test (pydantic-v2, 3.11) (push) Has been cancelled
Test / test (pydantic-v2, 3.12) (push) Has been cancelled
Test / test (pydantic-v2, 3.13) (push) Has been cancelled
Test / test (pydantic-v2, 3.14) (push) Has been cancelled
Test / test (pydantic-v2, 3.8) (push) Has been cancelled
Test / test (pydantic-v2, 3.9) (push) Has been cancelled
Test / coverage-combine (push) Has been cancelled
Test / check (push) Has been cancelled
Label Approved / label-approved (push) Has been cancelled
FastAPI People Contributors / job (push) Has been cancelled
FastAPI People Sponsors / job (push) Has been cancelled
Update Topic Repos / topic-repos (push) Has been cancelled
FastAPI People / job (push) Has been cancelled
Test / test (pydantic-v1, 3.12) (push) Has been cancelled

This commit is contained in:
The Scheduler
2025-12-07 21:35:20 +00:00
commit a42b1ff04e
2600 changed files with 301870 additions and 0 deletions
+503
View File
@@ -0,0 +1,503 @@
# Тестовый файл LLM { #llm-test-file }
Этот документ проверяет, понимает ли <abbr title="Large Language Model – Большая языковая модель">LLM</abbr>, переводящая документацию, `general_prompt` в `scripts/translate.py` и языковой специфичный промпт в `docs/{language code}/llm-prompt.md`. Языковой специфичный промпт добавляется к `general_prompt`.
Тесты, добавленные здесь, увидят все создатели языковых промптов.
Использование:
* Подготовьте языковой специфичный промпт — `docs/{language code}/llm-prompt.md`.
* Выполните новый перевод этого документа на нужный целевой язык (см., например, команду `translate-page` в `translate.py`). Это создаст перевод в `docs/{language code}/docs/_llm-test.md`.
* Проверьте, всё ли в порядке в переводе.
* При необходимости улучшите ваш языковой специфичный промпт, общий промпт или английский документ.
* Затем вручную исправьте оставшиеся проблемы в переводе, чтобы он был хорошим.
* Переведите заново, имея хороший перевод на месте. Идеальным результатом будет ситуация, когда LLM больше не вносит изменений в перевод. Это означает, что общий промпт и ваш языковой специфичный промпт максимально хороши (иногда он будет делать несколько, казалось бы, случайных изменений, причина в том, что <a href="https://doublespeak.chat/#/handbook#deterministic-output" class="external-link" target="_blank">LLM — недетерминированные алгоритмы</a>).
Тесты:
## Фрагменты кода { #code-snippets}
//// tab | Тест
Это фрагмент кода: `foo`. А это ещё один фрагмент кода: `bar`. И ещё один: `baz quux`.
////
//// tab | Информация
Содержимое фрагментов кода должно оставаться как есть.
См. раздел `### Content of code snippets` в общем промпте в `scripts/translate.py`.
////
## Кавычки { #quotes }
//// tab | Тест
Вчера мой друг написал: "Если вы написали incorrectly правильно, значит вы написали это неправильно". На что я ответил: "Верно, но 'incorrectly' — это неправильно, а не '"incorrectly"'".
/// note | Примечание
LLM, вероятно, переведёт это неправильно. Интересно лишь то, сохранит ли она фиксированный перевод при повторном переводе.
///
////
//// tab | Информация
Автор промпта может выбрать, хочет ли он преобразовывать нейтральные кавычки в типографские. Допускается оставить их как есть.
См., например, раздел `### Quotes` в `docs/de/llm-prompt.md`.
////
## Кавычки во фрагментах кода { #quotes-in-code-snippets}
//// tab | Тест
`pip install "foo[bar]"`
Примеры строковых литералов во фрагментах кода: `"this"`, `'that'`.
Сложный пример строковых литералов во фрагментах кода: `f"I like {'oranges' if orange else "apples"}"`
Хардкор: `Yesterday, my friend wrote: "If you spell incorrectly correctly, you have spelled it incorrectly". To which I answered: "Correct, but 'incorrectly' is incorrectly not '"incorrectly"'"`
////
//// tab | Информация
... Однако кавычки внутри фрагментов кода должны оставаться как есть.
////
## Блоки кода { #code-blocks }
//// tab | Тест
Пример кода Bash...
```bash
# Вывести приветствие вселенной
echo "Hello universe"
```
...и пример вывода в консоли...
```console
$ <font color="#4E9A06">fastapi</font> run <u style="text-decoration-style:solid">main.py</u>
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting server
Searching for package file structure
```
...и ещё один пример вывода в консоли...
```console
// Создать директорию "Code"
$ mkdir code
// Перейти в эту директорию
$ cd code
```
...и пример кода на Python...
```Python
wont_work() # Это не сработает 😱
works(foo="bar") # Это работает 🎉
```
...и на этом всё.
////
//// tab | Информация
Код в блоках кода не должен изменяться, за исключением комментариев.
См. раздел `### Content of code blocks` в общем промпте в `scripts/translate.py`.
////
## Вкладки и цветные блоки { #tabs-and-colored-boxes }
//// tab | Тест
/// info | Информация
Некоторый текст
///
/// note | Примечание
Некоторый текст
///
/// note | Технические подробности
Некоторый текст
///
/// check | Проверка
Некоторый текст
///
/// tip | Совет
Некоторый текст
///
/// warning | Предупреждение
Некоторый текст
///
/// danger | Опасность
Некоторый текст
///
////
//// tab | Информация
Для вкладок и блоков `Info`/`Note`/`Warning`/и т.п. нужно добавить перевод их заголовка после вертикальной черты (`|`).
См. разделы `### Special blocks` и `### Tab blocks` в общем промпте в `scripts/translate.py`.
////
## Веб- и внутренние ссылки { #web-and-internal-links }
//// tab | Тест
Текст ссылок должен переводиться, адрес ссылки не должен изменяться:
* [Ссылка на заголовок выше](#code-snippets)
* [Внутренняя ссылка](index.md#installation){.internal-link target=_blank}
* <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">Внешняя ссылка</a>
* <a href="https://fastapi.tiangolo.com/css/styles.css" class="external-link" target="_blank">Ссылка на стиль</a>
* <a href="https://fastapi.tiangolo.com/js/logic.js" class="external-link" target="_blank">Ссылка на скрипт</a>
* <a href="https://fastapi.tiangolo.com/img/foo.jpg" class="external-link" target="_blank">Ссылка на изображение</a>
Текст ссылок должен переводиться, адрес ссылки должен указывать на перевод:
* <a href="https://fastapi.tiangolo.com/ru/" class="external-link" target="_blank">Ссылка на FastAPI</a>
////
//// tab | Информация
Ссылки должны переводиться, но их адреса не должны изменяться. Исключение — абсолютные ссылки на страницы документации FastAPI. В этом случае ссылка должна вести на перевод.
См. раздел `### Links` в общем промпте в `scripts/translate.py`.
////
## HTML-элементы "abbr" { #html-abbr-elements }
//// tab | Тест
Вот некоторые элементы, обёрнутые в HTML-элементы "abbr" (часть выдумана):
### abbr даёт полную расшифровку { #the-abbr-gives-a-full-phrase }
* <abbr title="Getting Things Done Как привести дела в порядок">GTD</abbr>
* <abbr title="less than меньше чем"><code>lt</code></abbr>
* <abbr title="XML Web Token XML веб‑токен">XWT</abbr>
* <abbr title="Parallel Server Gateway Interface – Параллельный серверный интерфейс шлюза">PSGI</abbr>
### abbr даёт объяснение { #the-abbr-gives-an-explanation }
* <abbr title="Группа машин, которые настроены на соединение и совместную работу определённым образом.">кластер</abbr>
* <abbr title="Метод машинного обучения, который использует искусственные нейронные сети с многочисленными скрытыми слоями между входным и выходным слоями, тем самым формируя сложную внутреннюю структуру">Глубокое обучение</abbr>
### abbr даёт полную расшифровку и объяснение { #the-abbr-gives-a-full-phrase-and-an-explanation }
* <abbr title="Mozilla Developer Network – Сеть разработчиков Mozilla: документация для разработчиков, созданная командой Firefox">MDN</abbr>
* <abbr title="Input/Output Ввод/Вывод: чтение или запись на диск, сетевое взаимодействие.">I/O</abbr>.
////
//// tab | Информация
Атрибуты "title" элементов "abbr" переводятся по определённым правилам.
Переводы могут добавлять свои собственные элементы "abbr", которые LLM не должна удалять. Например, чтобы объяснить английские слова.
См. раздел `### HTML abbr elements` в общем промпте в `scripts/translate.py`.
////
## Заголовки { #headings }
//// tab | Тест
### Разработка веб‑приложения — руководство { #develop-a-webapp-a-tutorial }
Привет.
### Аннотации типов и -аннотации { #type-hints-and-annotations }
Снова привет.
### Супер- и подклассы { #super-and-subclasses }
Снова привет.
////
//// tab | Информация
Единственное жёсткое правило для заголовков — LLM должна оставить часть хеша в фигурных скобках без изменений, чтобы ссылки не ломались.
См. раздел `### Headings` в общем промпте в `scripts/translate.py`.
Для некоторых языковых инструкций см., например, раздел `### Headings` в `docs/de/llm-prompt.md`.
////
## Термины, используемые в документации { #terms-used-in-the-docs }
//// tab | Тест
* вы
* ваш
* например
* и т.д.
* `foo` как `int`
* `bar` как `str`
* `baz` как `list`
* Учебник — Руководство пользователя
* Расширенное руководство пользователя
* Документация по SQLModel
* Документация API
* Автоматическая документация
* Наука о данных
* Глубокое обучение
* Машинное обучение
* Внедрение зависимостей
* Аутентификация HTTP Basic
* HTTP Digest
* формат ISO
* стандарт JSON Schema
* JSON-схема
* определение схемы
* password flow
* Мобильный
* устаревший
* спроектированный
* некорректный
* на лету
* стандарт
* по умолчанию
* чувствительный к регистру
* нечувствительный к регистру
* обслуживать приложение
* отдавать страницу
* приложение
* приложение
* HTTP-запрос
* HTTP-ответ
* ответ с ошибкой
* операция пути
* декоратор операции пути
* функция-обработчик пути
* тело
* тело запроса
* тело ответа
* JSON-тело
* тело формы
* тело файла
* тело функции
* параметр
* body-параметр
* path-параметр
* query-параметр
* cookie-параметр
* параметр заголовка
* параметр формы
* параметр функции
* событие
* событие запуска
* запуск сервера
* событие остановки
* событие lifespan
* обработчик
* обработчик события
* обработчик исключений
* обрабатывать
* модель
* Pydantic-модель
* модель данных
* модель базы данных
* модель формы
* объект модели
* класс
* базовый класс
* родительский класс
* подкласс
* дочерний класс
* родственный класс
* метод класса
* заголовок
* HTTP-заголовки
* заголовок авторизации
* заголовок `Authorization`
* заголовок `Forwarded`
* система внедрения зависимостей
* зависимость
* зависимый объект
* зависимый
* ограниченный вводом/выводом
* ограниченный процессором
* конкурентность
* параллелизм
* многопроцессность
* переменная окружения
* переменная окружения
* `PATH`
* переменная `PATH`
* аутентификация
* провайдер аутентификации
* авторизация
* форма авторизации
* провайдер авторизации
* пользователь аутентифицируется
* система аутентифицирует пользователя
* CLI
* интерфейс командной строки
* сервер
* клиент
* облачный провайдер
* облачный сервис
* разработка
* этапы разработки
* dict
* словарь
* перечисление
* enum
* член перечисления
* кодировщик
* декодировщик
* кодировать
* декодировать
* исключение
* вызвать
* выражение
* оператор
* фронтенд
* бэкенд
* обсуждение на GitHub
* Issue на GitHub (тикет/обращение)
* производительность
* оптимизация производительности
* тип возвращаемого значения
* возвращаемое значение
* безопасность
* схема безопасности
* задача
* фоновая задача
* функция задачи
* шаблон
* шаблонизатор
* аннотация типов
* аннотация типов
* воркер сервера
* воркер Uvicorn
* воркер Gunicorn
* воркер-процесс
* класс воркера
* рабочая нагрузка
* деплой
* развернуть
* SDK
* набор средств разработки ПО
* `APIRouter`
* `requirements.txt`
* токен Bearer
* несовместимое изменение
* баг
* кнопка
* вызываемый объект
* код
* коммит
* менеджер контекста
* корутина
* сессия базы данных
* диск
* домен
* движок
* фиктивный X
* метод HTTP GET
* элемент
* библиотека
* lifespan
* блокировка
* middleware (Промежуточный слой)
* мобильное приложение
* модуль
* монтирование
* сеть
* origin (источник)
* переопределение
* полезная нагрузка
* процессор
* свойство
* прокси
* пулл-реквест (запрос на изменение)
* запрос
* ОЗУ
* удалённая машина
* статус-код
* строка
* тег
* веб‑фреймворк
* подстановочный знак
* вернуть
* валидировать
////
//// tab | Информация
Это неполный и ненормативный список (в основном) технических терминов, встречающихся в документации. Он может помочь автору промпта понять, по каким терминам LLM нужна подсказка. Например, когда она продолжает возвращать действительно хороший перевод к неоптимальному. Или когда у неё возникают проблемы со склонением/спряжением термина на вашем языке.
См., например, раздел `### List of English terms and their preferred German translations` в `docs/de/llm-prompt.md`.
////
+3
View File
@@ -0,0 +1,3 @@
# О проекте { #about }
О FastAPI, его дизайне, источниках вдохновения и многом другом. 🤓
@@ -0,0 +1,247 @@
# Дополнительные ответы в OpenAPI { #additional-responses-in-openapi }
/// warning | Предупреждение
Это довольно продвинутая тема.
Если вы только начинаете работать с **FastAPI**, возможно, вам это пока не нужно.
///
Вы можете объявлять дополнительные ответы с дополнительными статус-кодами, типами содержимого, описаниями и т.д.
Эти дополнительные ответы будут включены в схему OpenAPI, и поэтому появятся в документации API.
Но для таких дополнительных ответов убедитесь, что вы возвращаете `Response`, например `JSONResponse`, напрямую, со своим статус-кодом и содержимым.
## Дополнительный ответ с `model` { #additional-response-with-model }
Вы можете передать вашим декораторам операции пути параметр `responses`.
Он принимает `dict`: ключи — это статус-коды для каждого ответа (например, `200`), а значения — другие `dict` с информацией для каждого из них.
Каждый из этих `dict` для ответа может иметь ключ `model`, содержащий Pydantic-модель, аналогично `response_model`.
**FastAPI** возьмёт эту модель, сгенерирует для неё JSON‑схему и включит её в нужное место в OpenAPI.
Например, чтобы объявить ещё один ответ со статус-кодом `404` и Pydantic-моделью `Message`, можно написать:
{* ../../docs_src/additional_responses/tutorial001.py hl[18,22] *}
/// note | Примечание
Имейте в виду, что необходимо возвращать `JSONResponse` напрямую.
///
/// info | Информация
Ключ `model` не является частью OpenAPI.
**FastAPI** возьмёт Pydantic-модель оттуда, сгенерирует JSON‑схему и поместит её в нужное место.
Нужное место:
* В ключе `content`, значением которого является другой JSON‑объект (`dict`), содержащий:
* Ключ с типом содержимого, например `application/json`, значением которого является другой JSON‑объект, содержащий:
* Ключ `schema`, значением которого является JSON‑схема из модели — вот нужное место.
* **FastAPI** добавляет здесь ссылку на глобальные JSON‑схемы в другом месте вашего OpenAPI вместо того, чтобы включать схему напрямую. Так другие приложения и клиенты смогут использовать эти JSON‑схемы напрямую, предоставлять лучшие инструменты генерации кода и т.д.
///
Сгенерированные в OpenAPI ответы для этой операции пути будут такими:
```JSON hl_lines="3-12"
{
"responses": {
"404": {
"description": "Additional Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Message"
}
}
}
},
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Item"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
```
Схемы даны как ссылки на другое место внутри схемы OpenAPI:
```JSON hl_lines="4-16"
{
"components": {
"schemas": {
"Message": {
"title": "Message",
"required": [
"message"
],
"type": "object",
"properties": {
"message": {
"title": "Message",
"type": "string"
}
}
},
"Item": {
"title": "Item",
"required": [
"id",
"value"
],
"type": "object",
"properties": {
"id": {
"title": "Id",
"type": "string"
},
"value": {
"title": "Value",
"type": "string"
}
}
},
"ValidationError": {
"title": "ValidationError",
"required": [
"loc",
"msg",
"type"
],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"type": "string"
}
},
"msg": {
"title": "Message",
"type": "string"
},
"type": {
"title": "Error Type",
"type": "string"
}
}
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {
"$ref": "#/components/schemas/ValidationError"
}
}
}
}
}
}
}
```
## Дополнительные типы содержимого для основного ответа { #additional-media-types-for-the-main-response }
Вы можете использовать этот же параметр `responses`, чтобы добавить разные типы содержимого для того же основного ответа.
Например, вы можете добавить дополнительный тип содержимого `image/png`, объявив, что ваша операция пути может возвращать JSON‑объект (с типом содержимого `application/json`) или PNG‑изображение:
{* ../../docs_src/additional_responses/tutorial002.py hl[19:24,28] *}
/// note | Примечание
Учтите, что изображение нужно возвращать напрямую, используя `FileResponse`.
///
/// info | Информация
Если вы явно не укажете другой тип содержимого в параметре `responses`, FastAPI будет считать, что ответ имеет тот же тип содержимого, что и основной класс ответа (по умолчанию `application/json`).
Но если вы указали пользовательский класс ответа с `None` в качестве его типа содержимого, FastAPI использует `application/json` для любого дополнительного ответа, у которого есть связанная модель.
///
## Комбинирование информации { #combining-information }
Вы также можете комбинировать информацию об ответах из нескольких мест, включая параметры `response_model`, `status_code` и `responses`.
Вы можете объявить `response_model`, используя статус-код по умолчанию `200` (или свой, если нужно), а затем объявить дополнительную информацию для этого же ответа в `responses`, напрямую в схеме OpenAPI.
**FastAPI** сохранит дополнительную информацию из `responses` и объединит её с JSON‑схемой из вашей модели.
Например, вы можете объявить ответ со статус-кодом `404`, который использует Pydantic-модель и имеет пользовательское `description`.
А также ответ со статус-кодом `200`, который использует ваш `response_model`, но включает пользовательский `example`:
{* ../../docs_src/additional_responses/tutorial003.py hl[20:31] *}
Всё это будет объединено и включено в ваш OpenAPI и отображено в документации API:
<img src="/img/tutorial/additional-responses/image01.png">
## Комбинирование предопределённых и пользовательских ответов { #combine-predefined-responses-and-custom-ones }
Возможно, вы хотите иметь некоторые предопределённые ответы, применимые ко многим операциям пути, но при этом комбинировать их с пользовательскими ответами, необходимыми для каждой конкретной операции пути.
В таких случаях вы можете использовать приём Python «распаковки» `dict` с помощью `**dict_to_unpack`:
```Python
old_dict = {
"old key": "old value",
"second old key": "second old value",
}
new_dict = {**old_dict, "new key": "new value"}
```
Здесь `new_dict` будет содержать все пары ключ-значение из `old_dict` плюс новую пару ключ-значение:
```Python
{
"old key": "old value",
"second old key": "second old value",
"new key": "new value",
}
```
Вы можете использовать этот приём, чтобы переиспользовать некоторые предопределённые ответы в ваших операциях пути и комбинировать их с дополнительными пользовательскими.
Например:
{* ../../docs_src/additional_responses/tutorial004.py hl[13:17,26] *}
## Дополнительная информация об ответах OpenAPI { #more-information-about-openapi-responses }
Чтобы увидеть, что именно можно включать в ответы, посмотрите эти разделы спецификации OpenAPI:
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#responses-object" class="external-link" target="_blank">Объект Responses OpenAPI</a>, он включает `Response Object`.
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#response-object" class="external-link" target="_blank">Объект Response OpenAPI</a>, вы можете включить всё из этого объекта напрямую в каждый ответ внутри вашего параметра `responses`. Включая `description`, `headers`, `content` (внутри него вы объявляете разные типы содержимого и JSON‑схемы) и `links`.
@@ -0,0 +1,41 @@
# Дополнительные статус-коды { #additional-status-codes }
По умолчанию **FastAPI** будет возвращать ответы, используя `JSONResponse`, помещая содержимое, которое вы возвращаете из вашей *операции пути*, внутрь этого `JSONResponse`.
Он будет использовать статус-код по умолчанию или тот, который вы укажете в вашей *операции пути*.
## Дополнительные статус-коды { #additional-status-codes_1 }
Если вы хотите возвращать дополнительные статус-коды помимо основного, вы можете сделать это, возвращая `Response` напрямую, например `JSONResponse`, и устанавливая дополнительный статус-код напрямую.
Например, предположим, что вы хотите иметь *операцию пути*, которая позволяет обновлять элементы и возвращает HTTP статус-код 200 «OK» при успешном выполнении.
Но вы также хотите, чтобы она принимала новые элементы. И если элементы ранее не существовали, она создаёт их и возвращает HTTP статус-код 201 «Created».
Чтобы добиться этого, импортируйте `JSONResponse` и верните туда свой контент напрямую, установив нужный вам `status_code`:
{* ../../docs_src/additional_status_codes/tutorial001_an_py310.py hl[4,25] *}
/// warning | Внимание
Когда вы возвращаете `Response` напрямую, как в примере выше, он будет возвращён как есть.
Он не будет сериализован с помощью модели и т.п.
Убедитесь, что в нём именно те данные, которые вы хотите, и что значения являются валидным JSON (если вы используете `JSONResponse`).
///
/// note | Технические детали
Вы также можете использовать `from starlette.responses import JSONResponse`.
**FastAPI** предоставляет тот же `starlette.responses` через `fastapi.responses` просто для вашего удобства как разработчика. Но большинство доступных Response-классов приходят напрямую из Starlette. То же самое со `status`.
///
## OpenAPI и документация API { #openapi-and-api-docs }
Если вы возвращаете дополнительные статус-коды и ответы напрямую, они не будут включены в схему OpenAPI (документацию API), потому что у FastAPI нет способа заранее знать, что вы собираетесь вернуть.
Но вы можете задокументировать это в своём коде, используя: [Дополнительные ответы](additional-responses.md){.internal-link target=_blank}.
@@ -0,0 +1,163 @@
# Продвинутые зависимости { #advanced-dependencies }
## Параметризованные зависимости { #parameterized-dependencies }
Все зависимости, которые мы видели, — это конкретная функция или класс.
Но бывают случаи, когда нужно задавать параметры зависимости, не объявляя много разных функций или классов.
Представим, что нам нужна зависимость, которая проверяет, содержит ли query-параметр `q` некоторое фиксированное содержимое.
Но при этом мы хотим иметь возможность параметризовать это фиксированное содержимое.
## «Вызываемый» экземпляр { #a-callable-instance }
В Python есть способ сделать экземпляр класса «вызываемым» объектом.
Не сам класс (он уже является вызываемым), а экземпляр этого класса.
Для этого объявляем метод `__call__`:
{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[12] *}
В этом случае именно `__call__` **FastAPI** использует для проверки дополнительных параметров и подзависимостей, и именно он будет вызван, чтобы позже передать значение параметру в вашей *функции-обработчике пути*.
## Параметризуем экземпляр { #parameterize-the-instance }
Теперь мы можем использовать `__init__`, чтобы объявить параметры экземпляра, с помощью которых будем «параметризовать» зависимость:
{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[9] *}
В этом случае **FastAPI** вовсе не трогает `__init__` и не зависит от него — мы используем его напрямую в нашем коде.
## Создаём экземпляр { #create-an-instance }
Мы можем создать экземпляр этого класса так:
{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[18] *}
Так мы «параметризуем» нашу зависимость: теперь внутри неё хранится "bar" в атрибуте `checker.fixed_content`.
## Используем экземпляр как зависимость { #use-the-instance-as-a-dependency }
Затем мы можем использовать этот `checker` в `Depends(checker)` вместо `Depends(FixedContentQueryChecker)`, потому что зависимостью является экземпляр `checker`, а не сам класс.
И при разрешении зависимости **FastAPI** вызовет `checker` примерно так:
```Python
checker(q="somequery")
```
…и передаст возвращённое значение как значение зависимости в нашу *функцию-обработчике пути* в параметр `fixed_content_included`:
{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[22] *}
/// tip | Совет
Все это может показаться притянутым за уши. И пока может быть не совсем понятно, чем это полезно.
Эти примеры намеренно простые, но они показывают, как всё устроено.
В главах про безопасность есть вспомогательные функции, реализованные тем же способом.
Если вы поняли всё выше, вы уже знаете, как «под капотом» работают эти утилиты для безопасности.
///
## Зависимости с `yield`, `HTTPException`, `except` и фоновыми задачами { #dependencies-with-yield-httpexception-except-and-background-tasks }
/// warning | Предупреждение
Скорее всего, вам не понадобятся эти технические детали.
Они полезны главным образом, если у вас было приложение FastAPI версии ниже 0.121.0 и вы столкнулись с проблемами зависимостей с `yield`.
///
Зависимости с `yield` со временем изменялись, чтобы учитывать разные случаи применения и исправлять проблемы. Ниже — краткое резюме изменений.
### Зависимости с `yield` и `scope` { #dependencies-with-yield-and-scope }
В версии 0.121.0 FastAPI добавил поддержку `Depends(scope="function")` для зависимостей с `yield`.
При использовании `Depends(scope="function")` код после `yield` выполняется сразу после завершения *функции-обработчика пути*, до отправки ответа клиенту.
А при использовании `Depends(scope="request")` (значение по умолчанию) код после `yield` выполняется после отправки ответа.
Подробнее читайте в документации: [Зависимости с `yield` — раннее завершение и `scope`](../tutorial/dependencies/dependencies-with-yield.md#early-exit-and-scope).
### Зависимости с `yield` и `StreamingResponse`, технические детали { #dependencies-with-yield-and-streamingresponse-technical-details }
До FastAPI 0.118.0, если вы использовали зависимость с `yield`, код после `yield` выполнялся после возврата из *функции-обработчика пути*, но прямо перед отправкой ответа.
Идея состояла в том, чтобы не удерживать ресурсы дольше необходимого, пока ответ «путешествует» по сети.
Это изменение также означало, что если вы возвращали `StreamingResponse`, код после `yield` в зависимости уже успевал выполниться.
Например, если у вас была сессия базы данных в зависимости с `yield`, `StreamingResponse` не смог бы использовать эту сессию во время стриминга данных, потому что сессия уже была закрыта в коде после `yield`.
В версии 0.118.0 это поведение было возвращено к тому, что код после `yield` выполняется после отправки ответа.
/// info | Информация
Как вы увидите ниже, это очень похоже на поведение до версии 0.106.0, но с несколькими улучшениями и исправлениями краевых случаев.
///
#### Сценарии с ранним выполнением кода после `yield` { #use-cases-with-early-exit-code }
Есть некоторые сценарии со специфическими условиями, которым могло бы помочь старое поведение — выполнение кода после `yield` перед отправкой ответа.
Например, представьте, что вы используете сессию базы данных в зависимости с `yield` только для проверки пользователя, а в самой *функции-обработчике пути* эта сессия больше не используется, и при этом ответ отправляется долго, например, это `StreamingResponse`, который медленно отправляет данные и по какой-то причине не использует базу данных.
В таком случае сессия базы данных будет удерживаться до завершения отправки ответа, хотя если вы её не используете, удерживать её не требуется.
Это могло бы выглядеть так:
{* ../../docs_src/dependencies/tutorial013_an_py310.py *}
Код после `yield`, автоматическое закрытие `Session` в:
{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *}
…будет выполнен после того, как ответ закончит отправку медленных данных:
{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *}
Но поскольку `generate_stream()` не использует сессию базы данных, нет реальной необходимости держать сессию открытой во время отправки ответа.
Если у вас именно такой сценарий с SQLModel (или SQLAlchemy), вы можете явно закрыть сессию, когда она больше не нужна:
{* ../../docs_src/dependencies/tutorial014_an_py310.py ln[24:28] hl[28] *}
Так сессия освободит подключение к базе данных, и другие запросы смогут его использовать.
Если у вас есть другой сценарий, где нужно раннее завершение зависимости с `yield`, пожалуйста, создайте <a href="https://github.com/fastapi/fastapi/discussions/new?category=questions" class="external-link" target="_blank">вопрос в GitHub Discussions</a> с описанием конкретного кейса и почему вам было бы полезно иметь раннее закрытие для зависимостей с `yield`.
Если появятся веские причины для раннего закрытия в зависимостях с `yield`, я рассмотрю добавление нового способа опционально включать раннее закрытие.
### Зависимости с `yield` и `except`, технические детали { #dependencies-with-yield-and-except-technical-details }
До FastAPI 0.110.0, если вы использовали зависимость с `yield`, затем перехватывали исключение с `except` в этой зависимости и не пробрасывали исключение снова, исключение автоматически пробрасывалось дальше к обработчикам исключений или к обработчику внутренней ошибки сервера.
В версии 0.110.0 это было изменено, чтобы исправить неконтролируемое потребление памяти из‑за проброшенных исключений без обработчика (внутренние ошибки сервера) и привести поведение в соответствие с обычным поведением Python-кода.
### Фоновые задачи и зависимости с `yield`, технические детали { #background-tasks-and-dependencies-with-yield-technical-details }
До FastAPI 0.106.0 вызывать исключения после `yield` было невозможно: код после `yield` в зависимостях выполнялся уже после отправки ответа, поэтому [Обработчики исключений](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} к тому моменту уже отработали.
Так было сделано в основном для того, чтобы можно было использовать те же объекты, «отданные» зависимостями через `yield`, внутри фоновых задач, потому что код после `yield` выполнялся после завершения фоновых задач.
В FastAPI 0.106.0 это изменили, чтобы не удерживать ресурсы, пока ответ передаётся по сети.
/// tip | Совет
Кроме того, фоновая задача обычно — это самостоятельный фрагмент логики, который следует обрабатывать отдельно, со своими ресурсами (например, со своим подключением к базе данных).
Так код, скорее всего, будет чище.
///
Если вы полагались на прежнее поведение, теперь ресурсы для фоновых задач следует создавать внутри самой фоновой задачи и использовать внутри неё только данные, которые не зависят от ресурсов зависимостей с `yield`.
Например, вместо использования той же сессии базы данных, создайте новую сессию в фоновой задаче и получите объекты из базы данных с помощью этой новой сессии. И затем, вместо передачи объекта из базы данных параметром в функцию фоновой задачи, передавайте идентификатор этого объекта и заново получайте объект внутри функции фоновой задачи.
+99
View File
@@ -0,0 +1,99 @@
# Асинхронное тестирование { #async-tests }
Вы уже видели как тестировать **FastAPI** приложение, используя имеющийся класс `TestClient`. К этому моменту вы видели только как писать тесты в синхронном стиле без использования `async` функций.
Возможность использования асинхронных функций в ваших тестах может быть полезнa, когда, например, вы асинхронно обращаетесь к вашей базе данных. Представьте, что вы хотите отправить запросы в ваше FastAPI приложение, а затем при помощи асинхронной библиотеки для работы с базой данных удостовериться, что ваш бекэнд корректно записал данные в базу данных.
Давайте рассмотрим, как мы можем это реализовать.
## pytest.mark.anyio { #pytest-mark-anyio }
Если мы хотим вызывать асинхронные функции в наших тестах, то наши тестовые функции должны быть асинхронными. AnyIO предоставляет для этого отличный плагин, который позволяет нам указывать, какие тестовые функции должны вызываться асинхронно.
## HTTPX { #httpx }
Даже если **FastAPI** приложение использует обычные функции `def` вместо `async def`, это все равно `async` приложение 'под капотом'.
Чтобы работать с асинхронным FastAPI приложением в ваших обычных тестовых функциях `def`, используя стандартный pytest, `TestClient` внутри себя делает некоторую магию. Но эта магия перестает работать, когда мы используем его внутри асинхронных функций. Запуская наши тесты асинхронно, мы больше не можем использовать `TestClient` внутри наших тестовых функций.
`TestClient` основан на <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX</a>, и, к счастью, мы можем использовать его (`HTTPX`) напрямую для тестирования API.
## Пример { #example }
В качестве простого примера, давайте рассмотрим файловую структуру, схожую с описанной в [Большие приложения](../tutorial/bigger-applications.md){.internal-link target=_blank} и [Тестирование](../tutorial/testing.md){.internal-link target=_blank}:
```
.
├── app
│   ├── __init__.py
│   ├── main.py
│   └── test_main.py
```
Файл `main.py`:
{* ../../docs_src/async_tests/main.py *}
Файл `test_main.py` содержит тесты для `main.py`, теперь он может выглядеть так:
{* ../../docs_src/async_tests/test_main.py *}
## Запуск тестов { #run-it }
Вы можете запустить свои тесты как обычно:
<div class="termy">
```console
$ pytest
---> 100%
```
</div>
## Подробнее { #in-detail }
Маркер `@pytest.mark.anyio` говорит pytest, что тестовая функция должна быть вызвана асинхронно:
{* ../../docs_src/async_tests/test_main.py hl[7] *}
/// tip | Подсказка
Обратите внимание, что тестовая функция теперь `async def` вместо простого `def`, как это было при использовании `TestClient`.
///
Затем мы можем создать `AsyncClient` со ссылкой на приложение и посылать асинхронные запросы, используя `await`.
{* ../../docs_src/async_tests/test_main.py hl[9:12] *}
Это эквивалентно следующему:
```Python
response = client.get('/')
```
...которое мы использовали для отправки наших запросов с `TestClient`.
/// tip | Подсказка
Обратите внимание, что мы используем async/await с `AsyncClient` - запрос асинхронный.
///
/// warning | Внимание
Если ваше приложение полагается на lifespan события, то `AsyncClient` не запустит эти события. Чтобы обеспечить их срабатывание используйте `LifespanManager` из <a href="https://github.com/florimondmanca/asgi-lifespan#usage" class="external-link" target="_blank">florimondmanca/asgi-lifespan</a>.
///
## Вызов других асинхронных функций { #other-asynchronous-function-calls }
Теперь тестовая функция стала асинхронной, поэтому внутри нее вы можете вызывать также и другие `async` функции, не связанные с отправлением запросов в ваше FastAPI приложение. Как если бы вы вызывали их в любом другом месте вашего кода.
/// tip | Подсказка
Если вы столкнулись с `RuntimeError: Task attached to a different loop` при вызове асинхронных функций в ваших тестах (например, при использовании <a href="https://stackoverflow.com/questions/41584243/runtimeerror-task-attached-to-a-different-loop" class="external-link" target="_blank">MongoDB's MotorClient</a>), то не забывайте инициализировать объекты, которым нужен цикл событий (event loop), только внутри асинхронных функций, например, в `'@app.on_event("startup")` callback.
///
+458
View File
@@ -0,0 +1,458 @@
# За прокси‑сервером { #behind-a-proxy }
Во многих случаях перед приложением FastAPI используется прокси‑сервер, например Traefik или Nginx.
Такие прокси могут обрабатывать HTTPS‑сертификаты и многое другое.
## Пересылаемые заголовки прокси { #proxy-forwarded-headers }
Прокси перед вашим приложением обычно на лету добавляет некоторые HTTP‑заголовки перед отправкой запроса на ваш сервер, чтобы сообщить ему, что запрос был переслан прокси, а также передать исходный (публичный) URL (включая домен), информацию об использовании HTTPS и т.д.
Программа сервера (например, Uvicorn, запущенный через FastAPI CLI) умеет интерпретировать эти заголовки и передавать соответствующую информацию вашему приложению.
Но из соображений безопасности, пока сервер не уверен, что находится за доверенным прокси, он не будет интерпретировать эти заголовки.
/// note | Технические детали
Заголовки прокси:
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-For" class="external-link" target="_blank">X-Forwarded-For</a>
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Proto" class="external-link" target="_blank">X-Forwarded-Proto</a>
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Host" class="external-link" target="_blank">X-Forwarded-Host</a>
///
### Включить пересылаемые заголовки прокси { #enable-proxy-forwarded-headers }
Вы можете запустить FastAPI CLI с опцией командной строки `--forwarded-allow-ips` и передать IP‑адреса, которым следует доверять при чтении этих пересылаемых заголовков.
Если указать `--forwarded-allow-ips="*"`, приложение будет доверять всем входящим IP.
Если ваш сервер находится за доверенным прокси и только прокси обращается к нему, этого достаточно, чтобы он принимал IP этого прокси.
<div class="termy">
```console
$ fastapi run --forwarded-allow-ips="*"
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
### Редиректы с HTTPS { #redirects-with-https }
Например, вы объявили операцию пути `/items/`:
{* ../../docs_src/behind_a_proxy/tutorial001_01.py hl[6] *}
Если клиент обратится к `/items`, по умолчанию произойдёт редирект на `/items/`.
Но до установки опции `--forwarded-allow-ips` редирект может вести на `http://localhost:8000/items/`.
Однако приложение может быть доступно по `https://mysuperapp.com`, и редирект должен вести на `https://mysuperapp.com/items/`.
Указав `--proxy-headers`, FastAPI сможет редиректить на корректный адрес. 😎
```
https://mysuperapp.com/items/
```
/// tip | Совет
Если хотите узнать больше об HTTPS, смотрите руководство [О HTTPS](../deployment/https.md){.internal-link target=_blank}.
///
### Как работают пересылаемые заголовки прокси
Ниже показано, как прокси добавляет пересылаемые заголовки между клиентом и сервером приложения:
```mermaid
sequenceDiagram
participant Client as Клиент
participant Proxy as Прокси/Балансировщик нагрузки
participant Server as FastAPI-сервер
Client->>Proxy: HTTPS-запрос<br/>Host: mysuperapp.com<br/>Path: /items
Note over Proxy: Прокси-сервер добавляет пересылаемые заголовки
Proxy->>Server: HTTP-запрос<br/>X-Forwarded-For: [client IP]<br/>X-Forwarded-Proto: https<br/>X-Forwarded-Host: mysuperapp.com<br/>Path: /items
Note over Server: Server интерпретирует HTTP-заголовки<br/>(если --forwarded-allow-ips установлен)
Server->>Proxy: HTTP-ответ<br/>с верными HTTPS URLs
Proxy->>Client: HTTPS-ответ
```
Прокси перехватывает исходный клиентский запрос и добавляет специальные пересылаемые заголовки (`X-Forwarded-*`) перед передачей запроса на сервер приложения.
Эти заголовки сохраняют информацию об исходном запросе, которая иначе была бы потеряна:
* X-Forwarded-For: исходный IP‑адрес клиента
* X-Forwarded-Proto: исходный протокол (`https`)
* X-Forwarded-Host: исходный хост (`mysuperapp.com`)
Когда FastAPI CLI сконфигурирован с `--forwarded-allow-ips`, он доверяет этим заголовкам и использует их, например, чтобы формировать корректные URL в редиректах.
## Прокси с функцией удаления префикса пути { #proxy-with-a-stripped-path-prefix }
Прокси может добавлять к вашему приложению префикс пути (размещать приложение по пути с дополнительным префиксом).
В таких случаях вы можете использовать `root_path` для настройки приложения.
Механизм `root_path` определён спецификацией ASGI (на которой построен FastAPI, через Starlette).
`root_path` используется для обработки таких специфических случаев.
Он также используется внутри при монтировании вложенных приложений.
Прокси с функцией удаления префикса пути в этом случае означает, что вы объявляете путь `/app` в коде, а затем добавляете сверху слой (прокси), который размещает ваше приложение FastAPI под путём вида `/api/v1`.
Тогда исходный путь `/app` фактически будет обслуживаться по адресу `/api/v1/app`.
Хотя весь ваш код написан с расчётом, что путь один — `/app`.
{* ../../docs_src/behind_a_proxy/tutorial001.py hl[6] *}
Прокси будет «обрезать» префикс пути на лету перед передачей запроса на сервер приложения (скорее всего Uvicorn, запущенный через FastAPI CLI), поддерживая у вашего приложения иллюзию, что его обслуживают по `/app`, чтобы вам не пришлось менять весь код и добавлять префикс `/api/v1`.
До этого момента всё будет работать как обычно.
Но когда вы откроете встроенный интерфейс документации (фронтенд), он будет ожидать получить схему OpenAPI по адресу `/openapi.json`, а не `/api/v1/openapi.json`.
Поэтому фронтенд (который работает в браузере) попытается обратиться к `/openapi.json` и не сможет получить схему OpenAPI.
Так как для нашего приложения используется прокси с префиксом пути `/api/v1`, фронтенду нужно забирать схему OpenAPI по `/api/v1/openapi.json`.
```mermaid
graph LR
browser("Browser")
proxy["Proxy on http://0.0.0.0:9999/api/v1/app"]
server["Server on http://127.0.0.1:8000/app"]
browser --> proxy
proxy --> server
```
/// tip | Совет
IP `0.0.0.0` обычно означает, что программа слушает на всех IP‑адресах, доступных на этой машине/сервере.
///
Интерфейсу документации также нужна схема OpenAPI, в которой будет указано, что этот API `server` находится по пути `/api/v1` (за прокси). Например:
```JSON hl_lines="4-8"
{
"openapi": "3.1.0",
// Здесь ещё что-то
"servers": [
{
"url": "/api/v1"
}
],
"paths": {
// Здесь ещё что-то
}
}
```
В этом примере «Proxy» может быть, например, Traefik. А сервером будет что‑то вроде FastAPI CLI с Uvicorn, на котором запущено ваше приложение FastAPI.
### Указание `root_path` { #providing-the-root-path }
Для этого используйте опцию командной строки `--root-path`, например так:
<div class="termy">
```console
$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
Если вы используете Hypercorn, у него тоже есть опция `--root-path`.
/// note | Технические детали
Спецификация ASGI определяет `root_path` для такого случая.
А опция командной строки `--root-path` передаёт этот `root_path`.
///
### Проверка текущего `root_path` { #checking-the-current-root-path }
Вы можете получить текущий `root_path`, используемый вашим приложением для каждого запроса, — он входит в словарь `scope` (часть спецификации ASGI).
Здесь мы добавляем его в сообщение лишь для демонстрации.
{* ../../docs_src/behind_a_proxy/tutorial001.py hl[8] *}
Затем, если вы запустите Uvicorn так:
<div class="termy">
```console
$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
Ответ будет примерно таким:
```JSON
{
"message": "Hello World",
"root_path": "/api/v1"
}
```
### Установка `root_path` в приложении FastAPI { #setting-the-root-path-in-the-fastapi-app }
Если нет возможности передать опцию командной строки `--root-path` (или аналог), вы можете указать параметр `root_path` при создании приложения FastAPI:
{* ../../docs_src/behind_a_proxy/tutorial002.py hl[3] *}
Передача `root_path` в `FastAPI` эквивалентна опции командной строки `--root-path` для Uvicorn или Hypercorn.
### О `root_path` { #about-root-path }
Учтите, что сервер (Uvicorn) не использует `root_path` ни для чего, кроме как передать его в приложение.
Если вы откроете в браузере <a href="http://127.0.0.1:8000/app" class="external-link" target="_blank">http://127.0.0.1:8000/app</a>, вы увидите обычный ответ:
```JSON
{
"message": "Hello World",
"root_path": "/api/v1"
}
```
То есть он не ожидает, что к нему обратятся по адресу `http://127.0.0.1:8000/api/v1/app`.
Uvicorn ожидает, что прокси обратится к нему по `http://127.0.0.1:8000/app`, а уже задача прокси — добавить сверху префикс `/api/v1`.
## О прокси с урезанным префиксом пути { #about-proxies-with-a-stripped-path-prefix }
Помните, что прокси с урезанным префиксом пути — лишь один из вариантов настройки.
Во многих случаях по умолчанию прокси будет без урезанного префикса пути.
В таком случае (без урезанного префикса) прокси слушает, например, по адресу `https://myawesomeapp.com`, и если браузер идёт на `https://myawesomeapp.com/api/v1/app`, а ваш сервер (например, Uvicorn) слушает на `http://127.0.0.1:8000`, то прокси (без урезанного префикса) обратится к Uvicorn по тому же пути: `http://127.0.0.1:8000/api/v1/app`.
## Локальное тестирование с Traefik { #testing-locally-with-traefik }
Вы можете легко поэкспериментировать локально с урезанным префиксом пути, используя <a href="https://docs.traefik.io/" class="external-link" target="_blank">Traefik</a>.
<a href="https://github.com/containous/traefik/releases" class="external-link" target="_blank">Скачайте Traefik</a> — это один бинарный файл; распакуйте архив и запустите его прямо из терминала.
Затем создайте файл `traefik.toml` со следующим содержимым:
```TOML hl_lines="3"
[entryPoints]
[entryPoints.http]
address = ":9999"
[providers]
[providers.file]
filename = "routes.toml"
```
Это говорит Traefik слушать порт 9999 и использовать другой файл `routes.toml`.
/// tip | Совет
Мы используем порт 9999 вместо стандартного HTTP‑порта 80, чтобы не нужно было запускать с правами администратора (`sudo`).
///
Теперь создайте второй файл `routes.toml`:
```TOML hl_lines="5 12 20"
[http]
[http.middlewares]
[http.middlewares.api-stripprefix.stripPrefix]
prefixes = ["/api/v1"]
[http.routers]
[http.routers.app-http]
entryPoints = ["http"]
service = "app"
rule = "PathPrefix(`/api/v1`)"
middlewares = ["api-stripprefix"]
[http.services]
[http.services.app]
[http.services.app.loadBalancer]
[[http.services.app.loadBalancer.servers]]
url = "http://127.0.0.1:8000"
```
Этот файл настраивает Traefik на использование префикса пути `/api/v1`.
Далее Traefik будет проксировать запросы на ваш Uvicorn, работающий на `http://127.0.0.1:8000`.
Теперь запустите Traefik:
<div class="termy">
```console
$ ./traefik --configFile=traefik.toml
INFO[0000] Configuration loaded from file: /home/user/awesomeapi/traefik.toml
```
</div>
И запустите приложение с опцией `--root-path`:
<div class="termy">
```console
$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
### Проверьте ответы { #check-the-responses }
Теперь, если вы перейдёте на URL с портом Uvicorn: <a href="http://127.0.0.1:8000/app" class="external-link" target="_blank">http://127.0.0.1:8000/app</a>, вы увидите обычный ответ:
```JSON
{
"message": "Hello World",
"root_path": "/api/v1"
}
```
/// tip | Совет
Обратите внимание, что хотя вы обращаетесь по `http://127.0.0.1:8000/app`, в ответе указан `root_path` равный `/api/v1`, взятый из опции `--root-path`.
///
А теперь откройте URL с портом Traefik и префиксом пути: <a href="http://127.0.0.1:9999/api/v1/app" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/app</a>.
Мы получим тот же ответ:
```JSON
{
"message": "Hello World",
"root_path": "/api/v1"
}
```
но уже по URL с префиксом, который добавляет прокси: `/api/v1`.
Разумеется, задумывается, что все будут обращаться к приложению через прокси, поэтому вариант с префиксом пути `/api/v1` является «правильным».
А вариант без префикса (`http://127.0.0.1:8000/app`), выдаваемый напрямую Uvicorn, предназначен исключительно для того, чтобы прокси (Traefik) мог к нему обращаться.
Это демонстрирует, как прокси (Traefik) использует префикс пути и как сервер (Uvicorn) использует `root_path`, переданный через опцию `--root-path`.
### Проверьте интерфейс документации { #check-the-docs-ui }
А вот самое интересное. ✨
«Официальный» способ доступа к приложению — через прокси с заданным префиксом пути. Поэтому, как и ожидается, если открыть интерфейс документации, отдаваемый напрямую Uvicorn, без префикса пути в URL, он не будет работать, так как предполагается доступ через прокси.
Проверьте по адресу <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>:
<img src="/img/tutorial/behind-a-proxy/image01.png">
А вот если открыть интерфейс документации по «официальному» URL через прокси на порту `9999`, по `/api/v1/docs`, всё работает корректно! 🎉
Проверьте по адресу <a href="http://127.0.0.1:9999/api/v1/docs" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/docs</a>:
<img src="/img/tutorial/behind-a-proxy/image02.png">
Именно как и хотелось. ✔️
Это потому, что FastAPI использует `root_path`, чтобы создать в OpenAPI сервер по умолчанию с URL из `root_path`.
## Дополнительные серверы { #additional-servers }
/// warning | Предупреждение
Это более продвинутый сценарий. Можно пропустить.
///
По умолчанию FastAPI создаёт в схеме OpenAPI `server` с URL из `root_path`.
Но вы также можете указать дополнительные `servers`, например, если хотите, чтобы один и тот же интерфейс документации работал и со <abbr title="«промежуточное» или «предпродакшн» окружение">стейджингом</abbr>, и с продакшн.
Если вы передадите свой список `servers` и при этом задан `root_path` (потому что ваш API работает за прокси), FastAPI вставит «server» с этим `root_path` в начало списка.
Например:
{* ../../docs_src/behind_a_proxy/tutorial003.py hl[4:7] *}
Будет сгенерирована схема OpenAPI примерно такая:
```JSON hl_lines="5-7"
{
"openapi": "3.1.0",
// Здесь ещё что-то
"servers": [
{
"url": "/api/v1"
},
{
"url": "https://stag.example.com",
"description": "Staging environment"
},
{
"url": "https://prod.example.com",
"description": "Production environment"
}
],
"paths": {
// Здесь ещё что-то
}
}
```
/// tip | Совет
Обратите внимание на автоматически добавленный сервер с `url` равным `/api/v1`, взятым из `root_path`.
///
В интерфейсе документации по адресу <a href="http://127.0.0.1:9999/api/v1/docs" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/docs</a> это будет выглядеть так:
<img src="/img/tutorial/behind-a-proxy/image03.png">
/// tip | Совет
Интерфейс документации будет взаимодействовать с сервером, который вы выберете.
///
### Отключить автоматическое добавление сервера из `root_path` { #disable-automatic-server-from-root-path }
Если вы не хотите, чтобы FastAPI добавлял автоматический сервер, используя `root_path`, укажите параметр `root_path_in_servers=False`:
{* ../../docs_src/behind_a_proxy/tutorial004.py hl[9] *}
и тогда этот сервер не будет добавлен в схему OpenAPI.
## Монтирование вложенного приложения { #mounting-a-sub-application }
Если вам нужно смонтировать вложенное приложение (как описано в [Вложенные приложения — монтирование](sub-applications.md){.internal-link target=_blank}), и при этом вы используете прокси с `root_path`, делайте это обычным образом — всё будет работать, как ожидается.
FastAPI умно использует `root_path` внутри, так что всё просто работает. ✨
+312
View File
@@ -0,0 +1,312 @@
# Кастомные ответы — HTML, поток, файл и другие { #custom-response-html-stream-file-others }
По умолчанию **FastAPI** возвращает ответы с помощью `JSONResponse`.
Вы можете переопределить это, вернув `Response` напрямую, как показано в разделе [Вернуть Response напрямую](response-directly.md){.internal-link target=_blank}.
Но если вы возвращаете `Response` напрямую (или любой его подкласс, например `JSONResponse`), данные не будут автоматически преобразованы (даже если вы объявили `response_model`), и документация не будет автоматически сгенерирована (например, со специфичным «типом содержимого» в HTTP-заголовке `Content-Type` как частью сгенерированного OpenAPI).
Но вы можете также объявить `Response`, который хотите использовать (например, любой подкласс `Response`), в декораторе операции пути, используя параметр `response_class`.
Содержимое, которое вы возвращаете из своей функции-обработчика пути, будет помещено внутрь этого `Response`.
И если у этого `Response` тип содержимого JSON (`application/json`), как в случае с `JSONResponse` и `UJSONResponse`, данные, которые вы возвращаете, будут автоматически преобразованы (и отфильтрованы) любым объявленным вами в декораторе операции пути Pydantic `response_model`.
/// note | Примечание
Если вы используете класс ответа без типа содержимого, FastAPI будет ожидать, что у вашего ответа нет содержимого, поэтому он не будет документировать формат ответа в сгенерированной документации OpenAPI.
///
## Используйте `ORJSONResponse` { #use-orjsonresponse }
Например, если вы выжимаете максимум производительности, вы можете установить и использовать <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a> и задать ответ как `ORJSONResponse`.
Импортируйте класс (подкласс) `Response`, который вы хотите использовать, и объявите его в декораторе операции пути.
Для больших ответов возвращать `Response` напрямую значительно быстрее, чем возвращать словарь.
Это потому, что по умолчанию FastAPI проверяет каждый элемент внутри и убеждается, что он сериализуем в JSON, используя тот же [JSON Compatible Encoder](../tutorial/encoder.md){.internal-link target=_blank}, объяснённый в руководстве. Это позволяет возвращать **произвольные объекты**, например модели из базы данных.
Но если вы уверены, что содержимое, которое вы возвращаете, **сериализуемо в JSON**, вы можете передать его напрямую в класс ответа и избежать дополнительных накладных расходов, которые FastAPI понёс бы, пропуская возвращаемое содержимое через `jsonable_encoder` перед передачей в класс ответа.
{* ../../docs_src/custom_response/tutorial001b.py hl[2,7] *}
/// info | Информация
Параметр `response_class` также используется для указания «типа содержимого» ответа.
В этом случае HTTP-заголовок `Content-Type` будет установлен в `application/json`.
И это будет задокументировано как таковое в OpenAPI.
///
/// tip | Совет
`ORJSONResponse` доступен только в FastAPI, а не в Starlette.
///
## HTML-ответ { #html-response }
Чтобы вернуть ответ с HTML напрямую из **FastAPI**, используйте `HTMLResponse`.
- Импортируйте `HTMLResponse`.
- Передайте `HTMLResponse` в параметр `response_class` вашего декоратора операции пути.
{* ../../docs_src/custom_response/tutorial002.py hl[2,7] *}
/// info | Информация
Параметр `response_class` также используется для указания «типа содержимого» ответа.
В этом случае HTTP-заголовок `Content-Type` будет установлен в `text/html`.
И это будет задокументировано как таковое в OpenAPI.
///
### Вернуть `Response` { #return-a-response }
Как показано в разделе [Вернуть Response напрямую](response-directly.md){.internal-link target=_blank}, вы также можете переопределить ответ прямо в своей операции пути, просто вернув его.
Тот же пример сверху, возвращающий `HTMLResponse`, может выглядеть так:
{* ../../docs_src/custom_response/tutorial003.py hl[2,7,19] *}
/// warning | Предупреждение
`Response`, возвращённый напрямую вашей функцией-обработчиком пути, не будет задокументирован в OpenAPI (например, `Content-Type` нне будет задокументирова) и не будет виден в автоматически сгенерированной интерактивной документации.
///
/// info | Информация
Разумеется, фактические заголовок `Content-Type`, статус-код и т.д. возьмутся из объекта `Response`, который вы вернули.
///
### Задокументировать в OpenAPI и переопределить `Response` { #document-in-openapi-and-override-response }
Если вы хотите переопределить ответ внутри функции, но при этом задокументировать «тип содержимого» в OpenAPI, вы можете использовать параметр `response_class` И вернуть объект `Response`.
Тогда `response_class` будет использоваться только для документирования *операции пути* в OpenAPI, а ваш `Response` будет использован как есть.
#### Вернуть `HTMLResponse` напрямую { #return-an-htmlresponse-directly }
Например, это может быть что-то вроде:
{* ../../docs_src/custom_response/tutorial004.py hl[7,21,23] *}
В этом примере функция `generate_html_response()` уже генерирует и возвращает `Response` вместо возврата HTML в `str`.
Возвращая результат вызова `generate_html_response()`, вы уже возвращаете `Response`, который переопределит поведение **FastAPI** по умолчанию.
Но поскольку вы также передали `HTMLResponse` в `response_class`, **FastAPI** будет знать, как задокументировать это в OpenAPI и интерактивной документации как HTML с `text/html`:
<img src="/img/tutorial/custom-response/image01.png">
## Доступные ответы { #available-responses }
Ниже перечислены некоторые доступные классы ответов.
Учтите, что вы можете использовать `Response`, чтобы вернуть что угодно ещё, или даже создать собственный подкласс.
/// note | Технические детали
Вы также могли бы использовать `from starlette.responses import HTMLResponse`.
**FastAPI** предоставляет те же `starlette.responses` как `fastapi.responses` для вашего удобства как разработчика. Но большинство доступных классов ответов приходят непосредственно из Starlette.
///
### `Response` { #response }
Базовый класс `Response`, от него наследуются все остальные ответы.
Его можно возвращать напрямую.
Он принимает следующие параметры:
- `content``str` или `bytes`.
- `status_code` — целое число, HTTP статус-код.
- `headers` — словарь строк.
- `media_type` — строка, задающая тип содержимого. Например, `"text/html"`.
FastAPI (фактически Starlette) автоматически добавит заголовок Content-Length. Также будет добавлен заголовок Content-Type, основанный на `media_type` и с добавлением charset для текстовых типов.
{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *}
### `HTMLResponse` { #htmlresponse }
Принимает текст или байты и возвращает HTML-ответ, как описано выше.
### `PlainTextResponse` { #plaintextresponse }
Принимает текст или байты и возвращает ответ в виде простого текста.
{* ../../docs_src/custom_response/tutorial005.py hl[2,7,9] *}
### `JSONResponse` { #jsonresponse }
Принимает данные и возвращает ответ, кодированный как `application/json`.
Это ответ по умолчанию, используемый в **FastAPI**, как было сказано выше.
### `ORJSONResponse` { #orjsonresponse }
Быстрая альтернативная реализация JSON-ответа с использованием <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a>, как было сказано выше.
/// info | Информация
Требуется установка `orjson`, например командой `pip install orjson`.
///
### `UJSONResponse` { #ujsonresponse }
Альтернативная реализация JSON-ответа с использованием <a href="https://github.com/ultrajson/ultrajson" class="external-link" target="_blank">`ujson`</a>.
/// info | Информация
Требуется установка `ujson`, например командой `pip install ujson`.
///
/// warning | Предупреждение
`ujson` менее аккуратен, чем встроенная реализация Python, в обработке некоторых крайних случаев.
///
{* ../../docs_src/custom_response/tutorial001.py hl[2,7] *}
/// tip | Совет
Возможно, `ORJSONResponse` окажется более быстрым вариантом.
///
### `RedirectResponse` { #redirectresponse }
Возвращает HTTP-редирект. По умолчанию использует статус-код 307 (Temporary Redirect — временное перенаправление).
Вы можете вернуть `RedirectResponse` напрямую:
{* ../../docs_src/custom_response/tutorial006.py hl[2,9] *}
---
Или можно использовать его в параметре `response_class`:
{* ../../docs_src/custom_response/tutorial006b.py hl[2,7,9] *}
Если вы сделаете так, то сможете возвращать URL напрямую из своей функции-обработчика пути.
В этом случае будет использован статус-код по умолчанию для `RedirectResponse`, то есть `307`.
---
Также вы можете использовать параметр `status_code` в сочетании с параметром `response_class`:
{* ../../docs_src/custom_response/tutorial006c.py hl[2,7,9] *}
### `StreamingResponse` { #streamingresponse }
Принимает асинхронный генератор или обычный генератор/итератор и отправляет тело ответа потоково.
{* ../../docs_src/custom_response/tutorial007.py hl[2,14] *}
#### Использование `StreamingResponse` с файлоподобными объектами { #using-streamingresponse-with-file-like-objects }
Если у вас есть <a href="https://docs.python.org/3/glossary.html#term-file-like-object" class="external-link" target="_blank">файлоподобный</a> объект (например, объект, возвращаемый `open()`), вы можете создать функцию-генератор для итерации по этому файлоподобному объекту.
Таким образом, вам не нужно сначала читать всё в память, вы можете передать эту функцию-генератор в `StreamingResponse` и вернуть его.
Это включает многие библиотеки для работы с облачным хранилищем, обработки видео и т.д.
{* ../../docs_src/custom_response/tutorial008.py hl[2,10:12,14] *}
1. Это функция-генератор. Она является «функцией-генератором», потому что содержит оператор(ы) `yield` внутри.
2. Используя блок `with`, мы гарантируем, что файлоподобный объект будет закрыт после завершения работы функции-генератора. То есть после того, как она закончит отправку ответа.
3. Этот `yield from` говорит функции итерироваться по объекту с именем `file_like`. И затем, для каждой итерации, отдавать эту часть как исходящую из этой функции-генератора (`iterfile`).
Таким образом, это функция-генератор, которая внутренне передаёт работу по «генерации» чему-то другому.
Делая это таким образом, мы можем поместить её в блок `with` и тем самым гарантировать, что файлоподобный объект будет закрыт после завершения.
/// tip | Совет
Заметьте, что здесь мы используем стандартный `open()`, который не поддерживает `async` и `await`, поэтому объявляем операцию пути обычной `def`.
///
### `FileResponse` { #fileresponse }
Асинхронно отправляет файл как ответ.
Для создания экземпляра принимает иной набор аргументов, чем другие типы ответов:
- `path` — путь к файлу, который будет отправлен.
- `headers` — любые дополнительные заголовки для включения, в виде словаря.
- `media_type` — строка, задающая тип содержимого. Если не задан, для определения типа содержимого будет использовано имя файла или путь.
- `filename` — если задан, будет включён в заголовок ответа `Content-Disposition`.
Файловые ответы будут содержать соответствующие заголовки `Content-Length`, `Last-Modified` и `ETag`.
{* ../../docs_src/custom_response/tutorial009.py hl[2,10] *}
Вы также можете использовать параметр `response_class`:
{* ../../docs_src/custom_response/tutorial009b.py hl[2,8,10] *}
В этом случае вы можете возвращать путь к файлу напрямую из своей функции-обработчика пути.
## Пользовательский класс ответа { #custom-response-class }
Вы можете создать собственный класс ответа, унаследовавшись от `Response`, и использовать его.
Например, предположим, что вы хотите использовать <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a>, но с некоторыми пользовательскими настройками, которые не используются во встроенном классе `ORJSONResponse`.
Скажем, вы хотите, чтобы возвращался отформатированный JSON с отступами, то есть хотите использовать опцию orjson `orjson.OPT_INDENT_2`.
Вы могли бы создать `CustomORJSONResponse`. Главное, что вам нужно сделать — реализовать метод `Response.render(content)`, который возвращает содержимое как `bytes`:
{* ../../docs_src/custom_response/tutorial009c.py hl[9:14,17] *}
Теперь вместо того, чтобы возвращать:
```json
{"message": "Hello World"}
```
...этот ответ вернёт:
```json
{
"message": "Hello World"
}
```
Разумеется, вы наверняка найдёте гораздо более полезные способы воспользоваться этим, чем просто форматирование JSON. 😉
## Класс ответа по умолчанию { #default-response-class }
При создании экземпляра класса **FastAPI** или `APIRouter` вы можете указать, какой класс ответа использовать по умолчанию.
Параметр, который это определяет, — `default_response_class`.
В примере ниже **FastAPI** будет использовать `ORJSONResponse` по умолчанию во всех операциях пути вместо `JSONResponse`.
{* ../../docs_src/custom_response/tutorial010.py hl[2,4] *}
/// tip | Совет
Вы по-прежнему можете переопределять `response_class` в операциях пути, как и раньше.
///
## Дополнительная документация { #additional-documentation }
Вы также можете объявить тип содержимого и многие другие детали в OpenAPI с помощью `responses`: [Дополнительные ответы в OpenAPI](additional-responses.md){.internal-link target=_blank}.
+95
View File
@@ -0,0 +1,95 @@
# Использование dataclasses { #using-dataclasses }
FastAPI построен поверх **Pydantic**, и я показывал вам, как использовать Pydantic-модели для объявления HTTP-запросов и HTTP-ответов.
Но FastAPI также поддерживает использование <a href="https://docs.python.org/3/library/dataclasses.html" class="external-link" target="_blank">`dataclasses`</a> тем же способом:
{* ../../docs_src/dataclasses/tutorial001.py hl[1,7:12,19:20] *}
Это по-прежнему поддерживается благодаря **Pydantic**, так как в нём есть <a href="https://docs.pydantic.dev/latest/concepts/dataclasses/#use-of-stdlib-dataclasses-with-basemodel" class="external-link" target="_blank">встроенная поддержка `dataclasses`</a>.
Так что даже если в коде выше Pydantic не используется явно, FastAPI использует Pydantic, чтобы конвертировать стандартные dataclasses в собственный вариант dataclasses от Pydantic.
И, конечно, поддерживаются те же возможности:
- валидация данных
- сериализация данных
- документирование данных и т.д.
Это работает так же, как с Pydantic-моделями. И на самом деле под капотом это достигается тем же образом, с использованием Pydantic.
/// info | Информация
Помните, что dataclasses не умеют всего того, что умеют Pydantic-модели.
Поэтому вам всё ещё может потребоваться использовать Pydantic-модели.
Но если у вас уже есть набор dataclasses, это полезный приём — задействовать их для веб-API на FastAPI. 🤓
///
## Dataclasses в `response_model` { #dataclasses-in-response-model }
Вы также можете использовать `dataclasses` в параметре `response_model`:
{* ../../docs_src/dataclasses/tutorial002.py hl[1,7:13,19] *}
Этот dataclass будет автоматически преобразован в Pydantic dataclass.
Таким образом, его схема появится в интерфейсе документации API:
<img src="/img/tutorial/dataclasses/image01.png">
## Dataclasses во вложенных структурах данных { #dataclasses-in-nested-data-structures }
Вы также можете комбинировать `dataclasses` с другими аннотациями типов, чтобы создавать вложенные структуры данных.
В некоторых случаях вам всё же может понадобиться использовать версию `dataclasses` из Pydantic. Например, если у вас возникают ошибки с автоматически генерируемой документацией API.
В таком случае вы можете просто заменить стандартные `dataclasses` на `pydantic.dataclasses`, которая является полностью совместимой заменой (drop-in replacement):
{* ../../docs_src/dataclasses/tutorial003.py hl[1,5,8:11,14:17,23:25,28] *}
1. Мы по-прежнему импортируем `field` из стандартных `dataclasses`.
2. `pydantic.dataclasses` — полностью совместимая замена (drop-in replacement) для `dataclasses`.
3. Dataclass `Author` содержит список dataclass `Item`.
4. Dataclass `Author` используется в параметре `response_model`.
5. Вы можете использовать и другие стандартные аннотации типов вместе с dataclasses в качестве тела запроса.
В этом случае это список dataclass `Item`.
6. Здесь мы возвращаем словарь, содержащий `items`, который является списком dataclass.
FastAPI по-прежнему способен <abbr title="преобразование данных в формат, который можно передавать">сериализовать</abbr> данные в JSON.
7. Здесь `response_model` использует аннотацию типа — список dataclass `Author`.
Снова, вы можете комбинировать `dataclasses` со стандартными аннотациями типов.
8. Обратите внимание, что эта *функция-обработчик пути* использует обычный `def` вместо `async def`.
Как и всегда в FastAPI, вы можете сочетать `def` и `async def` по необходимости.
Если хотите освежить в памяти, когда что использовать, посмотрите раздел _"Нет времени?"_ в документации про [`async` и `await`](../async.md#in-a-hurry){.internal-link target=_blank}.
9. Эта *функция-обработчик пути* возвращает не dataclasses (хотя могла бы), а список словарей с внутренними данными.
FastAPI использует параметр `response_model` (в котором заданы dataclasses), чтобы преобразовать HTTP-ответ.
Вы можете комбинировать `dataclasses` с другими аннотациями типов множеством способов, чтобы формировать сложные структуры данных.
Смотрите подсказки в коде выше, чтобы увидеть более конкретные детали.
## Узнать больше { #learn-more }
Вы также можете комбинировать `dataclasses` с другими Pydantic-моделями, наследоваться от них, включать их в свои модели и т.д.
Чтобы узнать больше, посмотрите <a href="https://docs.pydantic.dev/latest/concepts/dataclasses/" class="external-link" target="_blank">документацию Pydantic о dataclasses</a>.
## Версия { #version }
Доступно начиная с версии FastAPI `0.67.0`. 🔖
+165
View File
@@ -0,0 +1,165 @@
# События lifespan { #lifespan-events }
Вы можете определить логику (код), которую нужно выполнить перед тем, как приложение начнет запускаться. Это означает, что этот код будет выполнен один раз, перед тем как приложение начнет получать HTTP-запросы.
Аналогично, вы можете определить логику (код), которую нужно выполнить, когда приложение завершает работу. В этом случае код будет выполнен один раз, после обработки, возможно, многих запросов.
Поскольку этот код выполняется до того, как приложение начинает принимать запросы, и сразу после того, как оно заканчивает их обрабатывать, он охватывает весь lifespan (жизненный цикл) приложения (слово «lifespan» станет важным через секунду 😉).
Это может быть очень полезно для настройки ресурсов, которые нужны для всего приложения, которые разделяются между запросами и/или которые нужно затем очистить. Например, пул подключений к базе данных или загрузка общей модели Машинного обучения.
## Вариант использования { #use-case }
Начнем с примера варианта использования, а затем посмотрим, как это решить.
Представим, что у вас есть несколько моделей Машинного обучения, которые вы хотите использовать для обработки запросов. 🤖
Эти же модели разделяются между запросами, то есть это не одна модель на запрос, не одна на пользователя и т.п.
Представим, что загрузка модели может занимать довольно много времени, потому что ей нужно прочитать много данных с диска. Поэтому вы не хотите делать это для каждого запроса.
Вы могли бы загрузить её на верхнем уровне модуля/файла, но это означало бы, что модель загружается даже если вы просто запускаете простой автоматический тест; тогда этот тест будет медленным, так как ему придется ждать загрузки модели перед запуском независимой части кода.
Именно это мы и решим: давайте загружать модель перед тем, как начнётся обработка запросов, но только непосредственно перед тем, как приложение начнет принимать запросы, а не во время загрузки кода.
## Lifespan { #lifespan }
Вы можете определить логику для startup и shutdown, используя параметр `lifespan` приложения `FastAPI` и «менеджер контекста» (через секунду покажу что это).
Начнем с примера, а затем разберём его подробнее.
Мы создаём асинхронную функцию `lifespan()` с `yield` примерно так:
{* ../../docs_src/events/tutorial003.py hl[16,19] *}
Здесь мы симулируем дорогую операцию startup по загрузке модели, помещая (фиктивную) функцию модели в словарь с моделями Машинного обучения до `yield`. Этот код будет выполнен до того, как приложение начнет принимать запросы, во время startup.
А затем сразу после `yield` мы выгружаем модель. Этот код будет выполнен после того, как приложение закончит обрабатывать запросы, непосредственно перед shutdown. Это может, например, освободить ресурсы, такие как память или GPU.
/// tip | Совет
`shutdown` произойдёт, когда вы останавливаете приложение.
Возможно, вам нужно запустить новую версию, или вы просто устали от него. 🤷
///
### Функция lifespan { #lifespan-function }
Первое, на что стоит обратить внимание, — мы определяем асинхронную функцию с `yield`. Это очень похоже на Зависимости с `yield`.
{* ../../docs_src/events/tutorial003.py hl[14:19] *}
Первая часть функции, до `yield`, будет выполнена до запуска приложения.
А часть после `yield` будет выполнена после завершения работы приложения.
### Асинхронный менеджер контекста { #async-context-manager }
Если присмотреться, функция декорирована `@asynccontextmanager`.
Это превращает функцию в «асинхронный менеджер контекста».
{* ../../docs_src/events/tutorial003.py hl[1,13] *}
Менеджер контекста в Python — это то, что можно использовать в операторе `with`. Например, `open()` можно использовать как менеджер контекста:
```Python
with open("file.txt") as file:
file.read()
```
В последних версиях Python есть также асинхронный менеджер контекста. Его используют с `async with`:
```Python
async with lifespan(app):
await do_stuff()
```
Когда вы создаёте менеджер контекста или асинхронный менеджер контекста, как выше, он перед входом в блок `with` выполнит код до `yield`, а после выхода из блока `with` выполнит код после `yield`.
В нашем примере выше мы не используем его напрямую, а передаём его в FastAPI, чтобы он использовал его сам.
Параметр `lifespan` приложения `FastAPI` принимает асинхронный менеджер контекста, поэтому мы можем передать ему наш новый асинхронный менеджер контекста `lifespan`.
{* ../../docs_src/events/tutorial003.py hl[22] *}
## Альтернативные события (устаревшие) { #alternative-events-deprecated }
/// warning | Предупреждение
Рекомендуемый способ обрабатывать startup и shutdown — использовать параметр `lifespan` приложения `FastAPI`, как описано выше. Если вы укажете параметр `lifespan`, обработчики событий `startup` и `shutdown` больше вызываться не будут. Либо всё через `lifespan`, либо всё через события — не одновременно.
Эту часть, скорее всего, можно пропустить.
///
Есть альтернативный способ определить логику, которую нужно выполнить во время startup и во время shutdown.
Вы можете определить обработчики событий (функции), которые нужно выполнить до старта приложения или при его завершении.
Эти функции можно объявить с `async def` или обычным `def`.
### Событие `startup` { #startup-event }
Чтобы добавить функцию, которую нужно запустить до старта приложения, объявите её как обработчик события `"startup"`:
{* ../../docs_src/events/tutorial001.py hl[8] *}
В этом случае функция-обработчик события `startup` инициализирует «базу данных» items (это просто `dict`) некоторыми значениями.
Вы можете добавить более одного обработчика события.
И ваше приложение не начнет принимать запросы, пока все обработчики события `startup` не завершатся.
### Событие `shutdown` { #shutdown-event }
Чтобы добавить функцию, которую нужно запустить при завершении работы приложения, объявите её как обработчик события `"shutdown"`:
{* ../../docs_src/events/tutorial002.py hl[6] *}
Здесь функция-обработчик события `shutdown` запишет строку текста `"Application shutdown"` в файл `log.txt`.
/// info | Информация
В функции `open()` параметр `mode="a"` означает «добавление» (append), то есть строка будет добавлена в конец файла, без перезаписи предыдущего содержимого.
///
/// tip | Совет
Обратите внимание, что в этом случае мы используем стандартную Python-функцию `open()`, которая взаимодействует с файлом.
То есть это I/O (ввод/вывод), требующий «ожидания» записи на диск.
Но `open()` не использует `async` и `await`.
Поэтому мы объявляем обработчик события обычным `def` вместо `async def`.
///
### `startup` и `shutdown` вместе { #startup-and-shutdown-together }
С высокой вероятностью логика для вашего startup и shutdown связана: вы можете хотеть что-то запустить, а затем завершить, получить ресурс, а затем освободить его и т.д.
Делать это в отдельных функциях, которые не разделяют общую логику или переменные, сложнее, так как придётся хранить значения в глобальных переменных или использовать похожие приёмы.
Поэтому теперь рекомендуется использовать `lifespan`, как описано выше.
## Технические детали { #technical-details }
Немного технических подробностей для любопытных умников. 🤓
Под капотом, в ASGI-технической спецификации, это часть <a href="https://asgi.readthedocs.io/en/latest/specs/lifespan.html" class="external-link" target="_blank">Протокола Lifespan</a>, и он определяет события `startup` и `shutdown`.
/// info | Информация
Вы можете прочитать больше про обработчики `lifespan` в Starlette в <a href="https://www.starlette.dev/lifespan/" class="external-link" target="_blank">документации Starlette по Lifespan</a>.
Включая то, как работать с состоянием lifespan, которое можно использовать в других частях вашего кода.
///
## Подприложения { #sub-applications }
🚨 Имейте в виду, что эти события lifespan (startup и shutdown) будут выполнены только для основного приложения, а не для [Подприложения — Mounts](sub-applications.md){.internal-link target=_blank}.
+208
View File
@@ -0,0 +1,208 @@
# Генерация SDK { #generating-sdks }
Поскольку **FastAPI** основан на спецификации **OpenAPI**, его API можно описать в стандартном формате, понятном множеству инструментов.
Это упрощает генерацию актуальной **документации**, клиентских библиотек (<abbr title="Software Development Kits – Наборы средств разработки">**SDKs**</abbr>) на разных языках, а также **тестирования** или **воркфлоу автоматизации**, которые остаются синхронизированными с вашим кодом.
В этом руководстве вы узнаете, как сгенерировать **TypeScript SDK** для вашего бэкенда на FastAPI.
## Генераторы SDK с открытым исходным кодом { #open-source-sdk-generators }
Гибкий вариант — <a href="https://openapi-generator.tech/" class="external-link" target="_blank">OpenAPI Generator</a>, который поддерживает **многие языки программирования** и умеет генерировать SDK из вашей спецификации OpenAPI.
Для **TypeScript‑клиентов** <a href="https://heyapi.dev/" class="external-link" target="_blank">Hey API</a> — специализированное решение, обеспечивающее оптимальный опыт для экосистемы TypeScript.
Больше генераторов SDK можно найти на <a href="https://openapi.tools/#sdk" class="external-link" target="_blank">OpenAPI.Tools</a>.
/// tip | Совет
FastAPI автоматически генерирует спецификации **OpenAPI 3.1**, поэтому любой используемый инструмент должен поддерживать эту версию.
///
## Генераторы SDK от спонсоров FastAPI { #sdk-generators-from-fastapi-sponsors }
В этом разделе представлены решения с **венчурной поддержкой** и **поддержкой компаний** от компаний, которые спонсируют FastAPI. Эти продукты предоставляют **дополнительные возможности** и **интеграции** сверх высококачественно генерируемых SDK.
Благодаря ✨ [**спонсорству FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨ эти компании помогают обеспечивать, чтобы фреймворк и его **экосистема** оставались здоровыми и **устойчивыми**.
Их спонсорство также демонстрирует серьёзную приверженность **сообществу** FastAPI (вам), показывая, что им важно не только предоставлять **отличный сервис**, но и поддерживать **надёжный и процветающий фреймворк** FastAPI. 🙇
Например, вы можете попробовать:
* <a href="https://speakeasy.com/editor?utm_source=fastapi+repo&utm_medium=github+sponsorship" class="external-link" target="_blank">Speakeasy</a>
* <a href="https://www.stainless.com/?utm_source=fastapi&utm_medium=referral" class="external-link" target="_blank">Stainless</a>
* <a href="https://developers.liblab.com/tutorials/sdk-for-fastapi?utm_source=fastapi" class="external-link" target="_blank">liblab</a>
Некоторые из этих решений также могут быть open source или иметь бесплатные тарифы, так что вы сможете попробовать их без финансовых затрат. Другие коммерческие генераторы SDK доступны и их можно найти онлайн. 🤓
## Создать TypeScript SDK { #create-a-typescript-sdk }
Начнём с простого приложения FastAPI:
{* ../../docs_src/generate_clients/tutorial001_py39.py hl[7:9,12:13,16:17,21] *}
Обратите внимание, что *операции пути (обработчики пути)* определяют модели, которые они используют для полезной нагрузки запроса и полезной нагрузки ответа, с помощью моделей `Item` и `ResponseMessage`.
### Документация API { #api-docs }
Если перейти на `/docs`, вы увидите **схемы** данных, отправляемых в запросах и принимаемых в ответах:
<img src="/img/tutorial/generate-clients/image01.png">
Вы видите эти схемы, потому что они были объявлены с моделями в приложении.
Эта информация доступна в **схеме OpenAPI** приложения и затем отображается в документации API.
Та же информация из моделей, включённая в OpenAPI, может использоваться для **генерации клиентского кода**.
### Hey API { #hey-api }
Как только у нас есть приложение FastAPI с моделями, мы можем использовать Hey API для генерации TypeScript‑клиента. Самый быстрый способ сделать это — через npx.
```sh
npx @hey-api/openapi-ts -i http://localhost:8000/openapi.json -o src/client
```
Это сгенерирует TypeScript SDK в `./src/client`.
Вы можете узнать, как <a href="https://heyapi.dev/openapi-ts/get-started" class="external-link" target="_blank">установить `@hey-api/openapi-ts`</a> и почитать о <a href="https://heyapi.dev/openapi-ts/output" class="external-link" target="_blank">сгенерированном результате</a> на их сайте.
### Использование SDK { #using-the-sdk }
Теперь вы можете импортировать и использовать клиентский код. Это может выглядеть так, обратите внимание, что вы получаете автозавершение для методoв:
<img src="/img/tutorial/generate-clients/image02.png">
Вы также получите автозавершение для отправляемой полезной нагрузки:
<img src="/img/tutorial/generate-clients/image03.png">
/// tip | Совет
Обратите внимание на автозавершение для `name` и `price`, это было определено в приложении FastAPI, в модели `Item`.
///
Вы получите ошибки прямо в редакторе для отправляемых данных:
<img src="/img/tutorial/generate-clients/image04.png">
Объект ответа также будет иметь автозавершение:
<img src="/img/tutorial/generate-clients/image05.png">
## Приложение FastAPI с тегами { #fastapi-app-with-tags }
Во многих случаях ваше приложение FastAPI будет больше, и вы, вероятно, будете использовать теги, чтобы разделять разные группы *операций пути*.
Например, у вас может быть раздел для **items** и другой раздел для **users**, и они могут быть разделены тегами:
{* ../../docs_src/generate_clients/tutorial002_py39.py hl[21,26,34] *}
### Генерация TypeScript‑клиента с тегами { #generate-a-typescript-client-with-tags }
Если вы генерируете клиент для приложения FastAPI с использованием тегов, обычно клиентский код также будет разделён по тегам.
Таким образом вы сможете иметь всё правильно упорядоченным и сгруппированным в клиентском коде:
<img src="/img/tutorial/generate-clients/image06.png">
В этом случае у вас есть:
* `ItemsService`
* `UsersService`
### Имена методов клиента { #client-method-names }
Сейчас сгенерированные имена методов вроде `createItemItemsPost` выглядят не очень аккуратно:
```TypeScript
ItemsService.createItemItemsPost({name: "Plumbus", price: 5})
```
...это потому, что генератор клиента использует внутренний **ID операции** OpenAPI для каждой *операции пути*.
OpenAPI требует, чтобы каждый ID операции был уникален среди всех *операций пути*, поэтому FastAPI использует **имя функции**, **путь** и **HTTP‑метод/операцию** для генерации этого ID операции, так как таким образом можно гарантировать уникальность ID операций.
Но далее я покажу, как это улучшить. 🤓
## Пользовательские ID операций и лучшие имена методов { #custom-operation-ids-and-better-method-names }
Вы можете **изменить** способ **генерации** этих ID операций, чтобы сделать их проще, а имена методов в клиентах — **более простыми**.
В этом случае вам нужно будет обеспечить, чтобы каждый ID операции был **уникальным** другим способом.
Например, вы можете гарантировать, что у каждой *операции пути* есть тег, и затем генерировать ID операции на основе **тега** и **имени** *операции пути* (имени функции).
### Пользовательская функция генерации уникального ID { #custom-generate-unique-id-function }
FastAPI использует **уникальный ID** для каждой *операции пути*, который применяется для **ID операции**, а также для имён любых необходимых пользовательских моделей запросов или ответов.
Вы можете кастомизировать эту функцию. Она принимает `APIRoute` и возвращает строку.
Например, здесь берётся первый тег (скорее всего у вас один тег) и имя *операции пути* (имя функции).
Затем вы можете передать эту пользовательскую функцию в **FastAPI** через параметр `generate_unique_id_function`:
{* ../../docs_src/generate_clients/tutorial003_py39.py hl[6:7,10] *}
### Генерация TypeScript‑клиента с пользовательскими ID операций { #generate-a-typescript-client-with-custom-operation-ids }
Теперь, если снова сгенерировать клиент, вы увидите, что имена методов улучшились:
<img src="/img/tutorial/generate-clients/image07.png">
Как видите, теперь имена методов содержат тег, а затем имя функции; больше они не включают информацию из URL‑пути и HTTP‑операции.
### Предобработка спецификации OpenAPI для генератора клиента { #preprocess-the-openapi-specification-for-the-client-generator }
Сгенерированном коде всё ещё есть **дублирующаяся информация**.
Мы уже знаем, что этот метод относится к **items**, потому что это слово есть в `ItemsService` (взято из тега), но при этом имя тега всё ещё добавлено префиксом к имени метода. 😕
Скорее всего мы захотим оставить это в OpenAPI в целом, так как это гарантирует, что ID операций будут **уникальны**.
Но для сгенерированного клиента мы можем **модифицировать** ID операций OpenAPI непосредственно перед генерацией клиентов, чтобы сделать имена методов более приятными и **чистыми**.
Мы можем скачать OpenAPI JSON в файл `openapi.json`, а затем **убрать этот префикс‑тег** таким скриптом:
{* ../../docs_src/generate_clients/tutorial004.py *}
//// tab | Node.js
```Javascript
{!> ../../docs_src/generate_clients/tutorial004.js!}
```
////
После этого ID операций будут переименованы с чего‑то вроде `items-get_items` просто в `get_items`, и генератор клиента сможет создавать более простые имена методов.
### Генерация TypeScript‑клиента с предобработанным OpenAPI { #generate-a-typescript-client-with-the-preprocessed-openapi }
Так как конечный результат теперь в файле `openapi.json`, нужно обновить входное расположение:
```sh
npx @hey-api/openapi-ts -i ./openapi.json -o src/client
```
После генерации нового клиента у вас будут **чистые имена методов**, со всем **автозавершением**, **ошибками прямо в редакторе** и т.д.:
<img src="/img/tutorial/generate-clients/image08.png">
## Преимущества { #benefits }
При использовании автоматически сгенерированных клиентов вы получите **автозавершение** для:
* Методов.
* Данных запроса — в теле запроса, query‑параметрах и т.д.
* Данных ответа.
У вас также будут **ошибки прямо в редакторе** для всего.
И каждый раз, когда вы обновляете код бэкенда и **перегенерируете** фронтенд, в нём появятся новые *операции пути* как методы, старые будут удалены, а любые другие изменения отразятся в сгенерированном коде. 🤓
Это также означает, что если что‑то изменилось, это будет **отражено** в клиентском коде автоматически. И если вы **соберёте** клиент, он завершится с ошибкой, если где‑то есть **несоответствие** в используемых данных.
Таким образом, вы **обнаружите многие ошибки** очень рано в цикле разработки, вместо того чтобы ждать, когда ошибки проявятся у конечных пользователей в продакшн, и затем пытаться отладить, в чём проблема. ✨
+21
View File
@@ -0,0 +1,21 @@
# Расширенное руководство пользователя { #advanced-user-guide }
## Дополнительные возможности { #additional-features }
Основное [Учебник - Руководство пользователя](../tutorial/index.md){.internal-link target=_blank} должно быть достаточно, чтобы познакомить вас со всеми основными функциями **FastAPI**.
В следующих разделах вы увидите другие варианты, конфигурации и дополнительные возможности.
/// tip | Совет
Следующие разделы **не обязательно являются "продвинутыми"**.
И вполне возможно, что для вашего случая использования решение находится в одном из них.
///
## Сначала прочитайте Учебник - Руководство пользователя { #read-the-tutorial-first }
Вы все еще можете использовать большинство функций **FastAPI** со знаниями из [Учебник - Руководство пользователя](../tutorial/index.md){.internal-link target=_blank}.
И следующие разделы предполагают, что вы уже прочитали его, и предполагают, что вы знаете эти основные идеи.
+97
View File
@@ -0,0 +1,97 @@
# Расширенное использование middleware { #advanced-middleware }
В основном руководстве вы читали, как добавить [пользовательское middleware](../tutorial/middleware.md){.internal-link target=_blank} в ваше приложение.
А затем — как работать с [CORS с помощью `CORSMiddleware`](../tutorial/cors.md){.internal-link target=_blank}.
В этом разделе посмотрим, как использовать другие middleware.
## Добавление ASGI middleware { #adding-asgi-middlewares }
Так как **FastAPI** основан на Starlette и реализует спецификацию <abbr title="Asynchronous Server Gateway Interface – Асинхронный шлюзовой интерфейс сервера">ASGI</abbr>, вы можете использовать любое ASGI middleware.
Middleware не обязательно должно быть сделано специально для FastAPI или Starlette — достаточно, чтобы оно соответствовало спецификации ASGI.
В общем случае ASGI middleware — это классы, которые ожидают получить ASGI‑приложение первым аргументом.
Поэтому в документации к сторонним ASGI middleware, скорее всего, вы увидите что‑то вроде:
```Python
from unicorn import UnicornMiddleware
app = SomeASGIApp()
new_app = UnicornMiddleware(app, some_config="rainbow")
```
Но FastAPI (точнее, Starlette) предоставляет более простой способ, который гарантирует корректную обработку внутренних ошибок сервера и корректную работу пользовательских обработчиков исключений.
Для этого используйте `app.add_middleware()` (как в примере с CORS).
```Python
from fastapi import FastAPI
from unicorn import UnicornMiddleware
app = FastAPI()
app.add_middleware(UnicornMiddleware, some_config="rainbow")
```
`app.add_middleware()` принимает класс middleware в качестве первого аргумента и любые дополнительные аргументы, которые будут переданы этому middleware.
## Встроенные middleware { #integrated-middlewares }
**FastAPI** включает несколько middleware для распространённых сценариев. Ниже рассмотрим, как их использовать.
/// note | Технические детали
В следующих примерах вы также можете использовать `from starlette.middleware.something import SomethingMiddleware`.
**FastAPI** предоставляет несколько middleware в `fastapi.middleware` для удобства разработчика. Но большинство доступных middleware приходит напрямую из Starlette.
///
## `HTTPSRedirectMiddleware` { #httpsredirectmiddleware }
Гарантирует, что все входящие запросы должны использовать либо `https`, либо `wss`.
Любой входящий запрос по `http` или `ws` будет перенаправлен на безопасную схему.
{* ../../docs_src/advanced_middleware/tutorial001.py hl[2,6] *}
## `TrustedHostMiddleware` { #trustedhostmiddleware }
Гарантирует, что во всех входящих запросах корректно установлен `Host`‑заголовок, чтобы защититься от атак на HTTP‑заголовок Host.
{* ../../docs_src/advanced_middleware/tutorial002.py hl[2,6:8] *}
Поддерживаются следующие аргументы:
- `allowed_hosts` — список доменных имён, которые следует разрешить как имена хостов. Подстановки вида `*.example.com` поддерживаются для сопоставления поддоменов. Чтобы разрешить любой хост, используйте либо `allowed_hosts=["*"]`, либо не добавляйте это middleware.
- `www_redirect` — если установлено в True, запросы к не‑www версиям разрешённых хостов будут перенаправляться на их www‑аналоги. По умолчанию — `True`.
Если входящий запрос не проходит валидацию, будет отправлен ответ `400`.
## `GZipMiddleware` { #gzipmiddleware }
Обрабатывает GZip‑ответы для любых запросов, которые включают `"gzip"` в заголовке `Accept-Encoding`.
Это middleware обрабатывает как обычные, так и потоковые ответы.
{* ../../docs_src/advanced_middleware/tutorial003.py hl[2,6] *}
Поддерживаются следующие аргументы:
- `minimum_size` — не сжимать GZip‑ом ответы, размер которых меньше этого минимального значения в байтах. По умолчанию — `500`.
- `compresslevel` — уровень GZip‑сжатия. Целое число от 1 до 9. По умолчанию — `9`. Более низкое значение — быстреее сжатие, но больший размер файла; более высокое значение — более медленное сжатие, но меньший размер файла.
## Другие middleware { #other-middlewares }
Существует много других ASGI middleware.
Например:
- <a href="https://github.com/encode/uvicorn/blob/master/uvicorn/middleware/proxy_headers.py" class="external-link" target="_blank">`ProxyHeadersMiddleware` от Uvicorn</a>
- <a href="https://github.com/florimondmanca/msgpack-asgi" class="external-link" target="_blank">MessagePack</a>
Чтобы увидеть другие доступные middleware, посмотрите <a href="https://www.starlette.dev/middleware/" class="external-link" target="_blank">документацию по middleware в Starlette</a> и <a href="https://github.com/florimondmanca/awesome-asgi" class="external-link" target="_blank">список ASGI Awesome</a>.
+186
View File
@@ -0,0 +1,186 @@
# Обратные вызовы в OpenAPI { #openapi-callbacks }
Вы можете создать API с *операцией пути* (обработчиком пути), которая будет инициировать HTTP-запрос к *внешнему API*, созданному кем-то другим (скорее всего тем же разработчиком, который будет использовать ваш API).
Процесс, происходящий, когда ваше приложение API обращается к *внешнему API*, называется «callback» (обратный вызов). Программное обеспечение, написанное внешним разработчиком, отправляет HTTP-запрос вашему API, а затем ваш API выполняет обратный вызов, отправляя HTTP-запрос во *внешний API* (который, вероятно, тоже создал тот же разработчик).
В этом случае вам может понадобиться задокументировать, как должно выглядеть это внешнее API: какую *операцию пути* оно должно иметь, какое тело запроса ожидать, какой ответ возвращать и т.д.
## Приложение с обратными вызовами { #an-app-with-callbacks }
Давайте рассмотрим это на примере.
Представьте, что вы разрабатываете приложение, позволяющее создавать счета.
Эти счета будут иметь `id`, `title` (необязательный), `customer` и `total`.
Пользователь вашего API (внешний разработчик) создаст счет в вашем API с помощью POST-запроса.
Затем ваш API (предположим) сделает следующее:
* Отправит счет клиенту внешнего разработчика.
* Получит оплату.
* Отправит уведомление обратно пользователю API (внешнему разработчику).
* Это будет сделано отправкой POST-запроса (из *вашего API*) в *внешний API*, предоставленный этим внешним разработчиком (это и есть «callback»).
## Обычное приложение **FastAPI** { #the-normal-fastapi-app }
Сначала посмотрим, как будет выглядеть обычное приложение API до добавления обратного вызова.
В нём будет *операция пути*, которая получит тело запроса `Invoice`, и query-параметр `callback_url`, содержащий URL для обратного вызова.
Эта часть вполне обычна, большая часть кода вам уже знакома:
{* ../../docs_src/openapi_callbacks/tutorial001.py hl[9:13,36:53] *}
/// tip | Совет
Query-параметр `callback_url` использует тип Pydantic <a href="https://docs.pydantic.dev/latest/api/networks/" class="external-link" target="_blank">Url</a>.
///
Единственное новое — это `callbacks=invoices_callback_router.routes` в качестве аргумента *декоратора операции пути*. Далее разберёмся, что это такое.
## Документирование обратного вызова { #documenting-the-callback }
Реальный код обратного вызова будет сильно зависеть от вашего приложения API.
И, вероятно, он будет заметно отличаться от одного приложения к другому.
Это могут быть буквально одна-две строки кода, например:
```Python
callback_url = "https://example.com/api/v1/invoices/events/"
httpx.post(callback_url, json={"description": "Invoice paid", "paid": True})
```
Но, возможно, самая важная часть обратного вызова — это убедиться, что пользователь вашего API (внешний разработчик) правильно реализует *внешний API* в соответствии с данными, которые *ваш API* будет отправлять в теле запроса обратного вызова и т.п.
Поэтому далее мы добавим код, документирующий, как должен выглядеть этот *внешний API*, чтобы получать обратный вызов от *вашего API*.
Эта документация отобразится в Swagger UI по адресу `/docs` в вашем API и позволит внешним разработчикам понять, как построить *внешний API*.
В этом примере сам обратный вызов не реализуется (это может быть всего одна строка кода), реализуется только часть с документацией.
/// tip | Совет
Сам обратный вызов — это всего лишь HTTP-запрос.
Реализуя обратный вызов, вы можете использовать, например, <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX</a> или <a href="https://requests.readthedocs.io/" class="external-link" target="_blank">Requests</a>.
///
## Напишите код документации обратного вызова { #write-the-callback-documentation-code }
Этот код не будет выполняться в вашем приложении, он нужен только для *документирования* того, как должен выглядеть *внешний API*.
Но вы уже знаете, как легко получить автоматическую документацию для API с **FastAPI**.
Мы используем те же знания, чтобы задокументировать, как должен выглядеть *внешний API*... создав *операции пути*, которые внешний API должен реализовать (те, которые ваш API будет вызывать).
/// tip | Совет
Когда вы пишете код для документирования обратного вызова, полезно представить, что вы — тот самый *внешний разработчик*. И что вы сейчас реализуете *внешний API*, а не *свой API*.
Временное принятие этой точки зрения (внешнего разработчика) поможет интуитивно понять, куда поместить параметры, какую Pydantic-модель использовать для тела запроса, для ответа и т.д. во *внешнем API*.
///
### Создайте `APIRouter` для обратного вызова { #create-a-callback-apirouter }
Сначала создайте новый `APIRouter`, который будет содержать один или несколько обратных вызовов.
{* ../../docs_src/openapi_callbacks/tutorial001.py hl[3,25] *}
### Создайте *операцию пути* для обратного вызова { #create-the-callback-path-operation }
Чтобы создать *операцию пути* для обратного вызова, используйте тот же `APIRouter`, который вы создали выше.
Она должна выглядеть как обычная *операция пути* FastAPI:
* Вероятно, в ней должно быть объявление тела запроса, например `body: InvoiceEvent`.
* А также может быть объявление модели ответа, например `response_model=InvoiceEventReceived`.
{* ../../docs_src/openapi_callbacks/tutorial001.py hl[16:18,21:22,28:32] *}
Есть 2 основных отличия от обычной *операции пути*:
* Ей не нужен реальный код, потому что ваше приложение никогда не будет вызывать эту функцию. Она используется только для документирования *внешнего API*. Поэтому в функции может быть просто `pass`.
* *Путь* может содержать <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#key-expression" class="external-link" target="_blank">выражение OpenAPI 3</a> (подробнее ниже), где можно использовать переменные с параметрами и части исходного HTTP-запроса, отправленного *вашему API*.
### Выражение пути для обратного вызова { #the-callback-path-expression }
*Путь* обратного вызова может содержать <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#key-expression" class="external-link" target="_blank">выражение OpenAPI 3</a>, которое может включать части исходного запроса, отправленного *вашему API*.
В нашем случае это `str`:
```Python
"{$callback_url}/invoices/{$request.body.id}"
```
Итак, если пользователь вашего API (внешний разработчик) отправляет HTTP-запрос вашему API по адресу:
```
https://yourapi.com/invoices/?callback_url=https://www.external.org/events
```
с телом JSON:
```JSON
{
"id": "2expen51ve",
"customer": "Mr. Richie Rich",
"total": "9999"
}
```
то *ваш API* обработает счёт и, в какой-то момент позже, отправит запрос обратного вызова на `callback_url` (*внешний API*):
```
https://www.external.org/events/invoices/2expen51ve
```
с телом JSON примерно такого вида:
```JSON
{
"description": "Payment celebration",
"paid": true
}
```
и будет ожидать от *внешнего API* ответ с телом JSON вида:
```JSON
{
"ok": true
}
```
/// tip | Совет
Обратите внимание, что используемый URL обратного вызова содержит URL, полученный как query-параметр в `callback_url` (`https://www.external.org/events`), а также `id` счёта из тела JSON (`2expen51ve`).
///
### Подключите маршрутизатор обратного вызова { #add-the-callback-router }
К этому моменту у вас есть необходимые *операции пути* обратного вызова (те, которые *внешний разработчик* должен реализовать во *внешнем API*) в созданном выше маршрутизаторе обратных вызовов.
Теперь используйте параметр `callbacks` в *декораторе операции пути вашего API*, чтобы передать атрибут `.routes` (это, по сути, просто `list` маршрутов/*операций пути*) из этого маршрутизатора обратных вызовов:
{* ../../docs_src/openapi_callbacks/tutorial001.py hl[35] *}
/// tip | Совет
Обратите внимание, что вы передаёте не сам маршрутизатор (`invoices_callback_router`) в `callback=`, а его атрибут `.routes`, то есть `invoices_callback_router.routes`.
///
### Проверьте документацию { #check-the-docs }
Теперь вы можете запустить приложение и перейти по адресу <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
Вы увидите документацию, включающую раздел «Callbacks» для вашей *операции пути*, который показывает, как должен выглядеть *внешний API*:
<img src="/img/tutorial/openapi-callbacks/image01.png">
+55
View File
@@ -0,0 +1,55 @@
# Вебхуки OpenAPI { #openapi-webhooks }
Бывают случаи, когда вы хотите сообщить пользователям вашего API, что ваше приложение может вызвать их приложение (отправив HTTP-запрос) с некоторыми данными, обычно чтобы уведомить о каком-то событии.
Это означает, что вместо обычного процесса, когда пользователи отправляют запросы вашему API, ваш API (или ваше приложение) может отправлять запросы в их систему (в их API, их приложение).
Обычно это называется вебхуком.
## Шаги вебхуков { #webhooks-steps }
Обычно процесс таков: вы определяете в своем коде, какое сообщение вы будете отправлять, то есть тело запроса.
Вы также определяете, в какие моменты (при каких событиях) ваше приложение будет отправлять эти запросы.
А ваши пользователи каким-то образом (например, в веб‑панели) указывают URL-адрес, на который ваше приложение должно отправлять эти запросы.
Вся логика регистрации URL-адресов для вебхуков и код, который реально отправляет эти запросы, целиком на вашей стороне. Вы пишете это так, как вам нужно, в своем собственном коде.
## Документирование вебхуков с помощью FastAPI и OpenAPI { #documenting-webhooks-with-fastapi-and-openapi }
С FastAPI, используя OpenAPI, вы можете определить имена этих вебхуков, типы HTTP-операций, которые ваше приложение может отправлять (например, `POST`, `PUT` и т.д.), а также тела запросов, которые ваше приложение будет отправлять.
Это значительно упростит вашим пользователям реализацию их API для приема ваших вебхук-запросов; возможно, они даже смогут автоматически сгенерировать часть кода своего API.
/// info | Информация
Вебхуки доступны в OpenAPI 3.1.0 и выше, поддерживаются в FastAPI `0.99.0` и новее.
///
## Приложение с вебхуками { #an-app-with-webhooks }
При создании приложения на **FastAPI** есть атрибут `webhooks`, с помощью которого можно объявлять вебхуки так же, как вы объявляете операции пути (обработчики пути), например с `@app.webhooks.post()`.
{* ../../docs_src/openapi_webhooks/tutorial001.py hl[9:13,36:53] *}
Определенные вами вебхуки попадут в схему **OpenAPI** и в автоматический **интерфейс документации**.
/// info | Информация
Объект `app.webhooks` на самом деле — это обычный `APIRouter`, тот же тип, который вы используете при структурировании приложения по нескольким файлам.
///
Обратите внимание: в случае с вебхуками вы на самом деле не объявляете путь (например, `/items/`), передаваемый туда текст — это лишь идентификатор вебхука (имя события). Например, в `@app.webhooks.post("new-subscription")` имя вебхука — `new-subscription`.
Это связано с тем, что предполагается: фактический URL‑путь, по которому они хотят получать запрос вебхука, ваши пользователи укажут каким-то другим образом (например, в веб‑панели).
### Посмотрите документацию { #check-the-docs }
Теперь вы можете запустить приложение и перейти по ссылке <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
Вы увидите, что в документации есть обычные операции пути, а также появились вебхуки:
<img src="/img/tutorial/openapi-webhooks/image01.png">
@@ -0,0 +1,204 @@
# Расширенная конфигурация операций пути { #path-operation-advanced-configuration }
## OpenAPI operationId { #openapi-operationid }
/// warning | Предупреждение
Если вы не «эксперт» по OpenAPI, скорее всего, это вам не нужно.
///
Вы можете задать OpenAPI `operationId`, который будет использоваться в вашей *операции пути*, с помощью параметра `operation_id`.
Нужно убедиться, что он уникален для каждой операции.
{* ../../docs_src/path_operation_advanced_configuration/tutorial001.py hl[6] *}
### Использование имени функции-обработчика пути как operationId { #using-the-path-operation-function-name-as-the-operationid }
Если вы хотите использовать имена функций ваших API в качестве `operationId`, вы можете пройти по всем из них и переопределить `operation_id` каждой *операции пути* с помощью их `APIRoute.name`.
Делать это следует после добавления всех *операций пути*.
{* ../../docs_src/path_operation_advanced_configuration/tutorial002.py hl[2, 12:21, 24] *}
/// tip | Совет
Если вы вызываете `app.openapi()` вручную, обновите `operationId` до этого.
///
/// warning | Предупреждение
Если вы делаете это, убедитесь, что каждая из ваших *функций-обработчиков пути* имеет уникальное имя.
Даже если они находятся в разных модулях (файлах Python).
///
## Исключить из OpenAPI { #exclude-from-openapi }
Чтобы исключить *операцию пути* из генерируемой схемы OpenAPI (а значит, и из автоматической документации), используйте параметр `include_in_schema` и установите его в `False`:
{* ../../docs_src/path_operation_advanced_configuration/tutorial003.py hl[6] *}
## Расширенное описание из docstring { #advanced-description-from-docstring }
Вы можете ограничить количество строк из docstring *функции-обработчика пути*, используемых для OpenAPI.
Добавление `\f` (экранированного символа «form feed») заставит **FastAPI** обрезать текст, используемый для OpenAPI, в этой точке.
Эта часть не попадёт в документацию, но другие инструменты (например, Sphinx) смогут использовать остальное.
{* ../../docs_src/path_operation_advanced_configuration/tutorial004.py hl[19:29] *}
## Дополнительные ответы { #additional-responses }
Вы, вероятно, уже видели, как объявлять `response_model` и `status_code` для *операции пути*.
Это определяет метаданные об основном ответе *операции пути*.
Также можно объявлять дополнительные ответы с их моделями, статус-кодами и т.д.
В документации есть целая глава об этом — [Дополнительные ответы в OpenAPI](additional-responses.md){.internal-link target=_blank}.
## Дополнительные данные OpenAPI { #openapi-extra }
Когда вы объявляете *операцию пути* в своём приложении, **FastAPI** автоматически генерирует соответствующие метаданные об этой *операции пути* для включения в схему OpenAPI.
/// note | Технические детали
В спецификации OpenAPI это называется <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#operation-object" class="external-link" target="_blank">Объект операции</a>.
///
Он содержит всю информацию об *операции пути* и используется для генерации автоматической документации.
Там есть `tags`, `parameters`, `requestBody`, `responses` и т.д.
Эта спецификация OpenAPI, специфичная для *операции пути*, обычно генерируется автоматически **FastAPI**, но вы также можете её расширить.
/// tip | Совет
Это низкоуровневая возможность расширения.
Если вам нужно лишь объявить дополнительные ответы, удобнее сделать это через [Дополнительные ответы в OpenAPI](additional-responses.md){.internal-link target=_blank}.
///
Вы можете расширить схему OpenAPI для *операции пути* с помощью параметра `openapi_extra`.
### Расширения OpenAPI { #openapi-extensions }
`openapi_extra` может пригодиться, например, чтобы объявить [Расширения OpenAPI](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions):
{* ../../docs_src/path_operation_advanced_configuration/tutorial005.py hl[6] *}
Если вы откроете автоматическую документацию API, ваше расширение появится внизу страницы конкретной *операции пути*.
<img src="/img/tutorial/path-operation-advanced-configuration/image01.png">
И если вы посмотрите на итоговый OpenAPI (по адресу `/openapi.json` вашего API), вы также увидите своё расширение в составе описания соответствующей *операции пути*:
```JSON hl_lines="22"
{
"openapi": "3.1.0",
"info": {
"title": "FastAPI",
"version": "0.1.0"
},
"paths": {
"/items/": {
"get": {
"summary": "Read Items",
"operationId": "read_items_items__get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
}
},
"x-aperture-labs-portal": "blue"
}
}
}
}
```
### Пользовательская схема OpenAPI для операции пути { #custom-openapi-path-operation-schema }
Словарь в `openapi_extra` будет объединён с автоматически сгенерированной схемой OpenAPI для *операции пути*.
Таким образом, вы можете добавить дополнительные данные к автоматически сгенерированной схеме.
Например, вы можете решить читать и валидировать запрос своим кодом, не используя автоматические возможности FastAPI и Pydantic, но при этом захотите описать запрос в схеме OpenAPI.
Это можно сделать с помощью `openapi_extra`:
{* ../../docs_src/path_operation_advanced_configuration/tutorial006.py hl[19:36, 39:40] *}
В этом примере мы не объявляли никакую Pydantic-модель. Фактически тело запроса даже не <abbr title="преобразовано из простого формата, например байтов, в объекты Python">распарсено</abbr> как JSON, оно читается напрямую как `bytes`, а функция `magic_data_reader()` будет отвечать за его парсинг каким-то способом.
Тем не менее, мы можем объявить ожидаемую схему для тела запроса.
### Пользовательский тип содержимого в OpenAPI { #custom-openapi-content-type }
Используя тот же приём, вы можете воспользоваться Pydantic-моделью, чтобы определить JSON Schema, которая затем будет включена в пользовательский раздел схемы OpenAPI для *операции пути*.
И вы можете сделать это, даже если тип данных в запросе — не JSON.
Например, в этом приложении мы не используем встроенную функциональность FastAPI для извлечения JSON Schema из моделей Pydantic, равно как и автоматическую валидацию JSON. Мы объявляем тип содержимого запроса как YAML, а не JSON:
//// tab | Pydantic v2
{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[17:22, 24] *}
////
//// tab | Pydantic v1
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py hl[17:22, 24] *}
////
/// info | Информация
В Pydantic версии 1 метод для получения JSON Schema модели назывался `Item.schema()`, в Pydantic версии 2 метод называется `Item.model_json_schema()`.
///
Тем не менее, хотя мы не используем встроенную функциональность по умолчанию, мы всё равно используем Pydantic-модель, чтобы вручную сгенерировать JSON Schema для данных, которые мы хотим получить в YAML.
Затем мы работаем с запросом напрямую и извлекаем тело как `bytes`. Это означает, что FastAPI даже не попытается распарсить полезную нагрузку запроса как JSON.
А затем в нашем коде мы напрямую парсим этот YAML и снова используем ту же Pydantic-модель для валидации YAML-содержимого:
//// tab | Pydantic v2
{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[26:33] *}
////
//// tab | Pydantic v1
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py hl[26:33] *}
////
/// info | Информация
В Pydantic версии 1 метод для парсинга и валидации объекта назывался `Item.parse_obj()`, в Pydantic версии 2 метод называется `Item.model_validate()`.
///
/// tip | Совет
Здесь мы переиспользуем ту же Pydantic-модель.
Но аналогично мы могли бы валидировать данные и каким-то другим способом.
///
@@ -0,0 +1,31 @@
# Response - Изменение статус-кода { #response-change-status-code }
Вы, вероятно, уже читали о том, что можно установить [статус-код ответа по умолчанию](../tutorial/response-status-code.md){.internal-link target=_blank}.
Но в некоторых случаях нужно вернуть другой статус-код, отличный от значения по умолчанию.
## Пример использования { #use-case }
Например, представьте, что вы хотите по умолчанию возвращать HTTP статус-код «OK» `200`.
Но если данные не существовали, вы хотите создать их и вернуть HTTP статус-код «CREATED» `201`.
При этом вы всё ещё хотите иметь возможность фильтровать и преобразовывать возвращаемые данные с помощью `response_model`.
Для таких случаев вы можете использовать параметр `Response`.
## Использование параметра `Response` { #use-a-response-parameter }
Вы можете объявить параметр типа `Response` в вашей *функции обработки пути* (как и для cookies и HTTP-заголовков).
И затем вы можете установить `status_code` в этом *временном* объекте ответа.
{* ../../docs_src/response_change_status_code/tutorial001.py hl[1,9,12] *}
После этого вы можете вернуть любой объект, который вам нужен, как обычно (`dict`, модель базы данных и т.д.).
И если вы объявили `response_model`, он всё равно будет использоваться для фильтрации и преобразования возвращаемого объекта.
**FastAPI** будет использовать этот *временный* ответ для извлечения статус-кода (а также cookies и HTTP-заголовков) и поместит их в финальный ответ, который содержит возвращаемое вами значение, отфильтрованное любым `response_model`.
Вы также можете объявить параметр `Response` в зависимостях и установить в них статус-код. Но помните, что последнее установленное значение будет иметь приоритет.
+51
View File
@@ -0,0 +1,51 @@
# Cookies в ответе { #response-cookies }
## Использование параметра `Response` { #use-a-response-parameter }
Вы можете объявить параметр типа `Response` в вашей функции-обработчике пути.
Затем установить cookies в этом временном объекте ответа.
{* ../../docs_src/response_cookies/tutorial002.py hl[1, 8:9] *}
После этого можно вернуть любой объект, как и раньше (например, `dict`, объект модели базы данных и так далее).
Если вы указали `response_model`, он всё равно будет использоваться для фильтрации и преобразования возвращаемого объекта.
**FastAPI** извлечет cookies (а также HTTP-заголовки и статус-код) из временного ответа и включит их в окончательный ответ, содержащий ваше возвращаемое значение, отфильтрованное через `response_model`.
Вы также можете объявить параметр типа `Response` в зависимостях и устанавливать cookies (и HTTP-заголовки) там.
## Возвращение `Response` напрямую { #return-a-response-directly }
Вы также можете установить Cookies, если возвращаете `Response` напрямую в вашем коде.
Для этого создайте объект `Response`, как описано в разделе [Возвращение ответа напрямую](response-directly.md){.internal-link target=_blank}.
Затем установите cookies и верните этот объект:
{* ../../docs_src/response_cookies/tutorial001.py hl[10:12] *}
/// tip | Совет
Имейте в виду, что если вы возвращаете ответ напрямую, вместо использования параметра `Response`, FastAPI вернёт его напрямую.
Убедитесь, что ваши данные имеют корректный тип. Например, они должны быть совместимы с JSON, если вы возвращаете `JSONResponse`.
Также убедитесь, что вы не отправляете данные, которые должны были быть отфильтрованы через `response_model`.
///
### Дополнительная информация { #more-info }
/// note | Технические детали
Вы также можете использовать `from starlette.responses import Response` или `from starlette.responses import JSONResponse`.
**FastAPI** предоставляет `fastapi.responses`, которые являются теми же объектами, что и `starlette.responses`, просто для удобства. Однако большинство доступных типов ответов поступает непосредственно из **Starlette**.
И так как `Response` часто используется для установки HTTP-заголовков и cookies, **FastAPI** также предоставляет его как `fastapi.Response`.
///
Чтобы увидеть все доступные параметры и настройки, ознакомьтесь с <a href="https://www.starlette.dev/responses/#set-cookie" class="external-link" target="_blank">документацией Starlette</a>.
@@ -0,0 +1,65 @@
# Возврат ответа напрямую { #return-a-response-directly }
Когда вы создаёте **FastAPI** *операцию пути*, вы можете возвращать из неё любые данные: `dict`, `list`, Pydantic-модель, модель базы данных и т.д.
По умолчанию **FastAPI** автоматически преобразует возвращаемое значение в JSON с помощью `jsonable_encoder`, как описано в [JSON кодировщик](../tutorial/encoder.md){.internal-link target=_blank}.
Затем "под капотом" эти данные, совместимые с JSON (например `dict`), помещаются в `JSONResponse`, который используется для отправки ответа клиенту.
Но вы можете возвращать `JSONResponse` напрямую из ваших *операций пути*.
Это может быть полезно, например, если нужно вернуть пользовательские HTTP-заголовки или cookie.
## Возврат `Response` { #return-a-response }
На самом деле, вы можете возвращать любой объект `Response` или его подкласс.
/// tip | Подсказка
`JSONResponse` сам по себе является подклассом `Response`.
///
И когда вы возвращаете `Response`, **FastAPI** передаст его напрямую.
Это не приведет к преобразованию данных с помощью Pydantic-моделей, содержимое не будет преобразовано в какой-либо тип и т.д.
Это даёт вам большую гибкость. Вы можете возвращать любые типы данных, переопределять любые объявления или валидацию данных и т.д.
## Использование `jsonable_encoder` в `Response` { #using-the-jsonable-encoder-in-a-response }
Поскольку **FastAPI** не изменяет объект `Response`, который вы возвращаете, вы должны убедиться, что его содержимое готово к отправке.
Например, вы не можете поместить Pydantic-модель в `JSONResponse`, не преобразовав её сначала в `dict` с помощью преобразования всех типов данных (таких как `datetime`, `UUID` и т.д.) в совместимые с JSON типы.
В таких случаях вы можете использовать `jsonable_encoder` для преобразования данных перед передачей их в ответ:
{* ../../docs_src/response_directly/tutorial001.py hl[6:7,21:22] *}
/// note | Технические детали
Вы также можете использовать `from starlette.responses import JSONResponse`.
**FastAPI** предоставляет `starlette.responses` через `fastapi.responses` просто для вашего удобства, как разработчика. Но большинство доступных Response-классов поступают напрямую из Starlette.
///
## Возврат пользовательского `Response` { #returning-a-custom-response }
Пример выше показывает все необходимые части, но он пока не очень полезен, так как вы могли бы просто вернуть `item` напрямую, и **FastAPI** поместил бы его в `JSONResponse`, преобразовав в `dict` и т.д. Всё это происходит по умолчанию.
Теперь давайте посмотрим, как можно использовать это для возврата пользовательского ответа.
Допустим, вы хотите вернуть ответ в формате <a href="https://en.wikipedia.org/wiki/XML" class="external-link" target="_blank">XML</a>.
Вы можете поместить ваш XML-контент в строку, поместить её в `Response` и вернуть:
{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *}
## Примечания { #notes }
Когда вы возвращаете объект `Response` напрямую, его данные не валидируются, не преобразуются (не сериализуются) и не документируются автоматически.
Но вы всё равно можете задокументировать это, как описано в [Дополнительные ответы в OpenAPI](additional-responses.md){.internal-link target=_blank}.
В следующих разделах вы увидите, как использовать/объявлять такие кастомные `Response`, при этом сохраняя автоматическое преобразование данных, документацию и т.д.
+41
View File
@@ -0,0 +1,41 @@
# HTTP-заголовки ответа { #response-headers }
## Использовать параметр `Response` { #use-a-response-parameter }
Вы можете объявить параметр типа `Response` в вашей функции-обработчике пути (как можно сделать и для cookie).
А затем вы можете устанавливать HTTP-заголовки в этом *временном* объекте ответа.
{* ../../docs_src/response_headers/tutorial002.py hl[1, 7:8] *}
После этого вы можете вернуть любой нужный объект, как обычно (например, `dict`, модель из базы данных и т.д.).
И, если вы объявили `response_model`, он всё равно будет использован для фильтрации и преобразования возвращённого объекта.
**FastAPI** использует этот *временный* ответ, чтобы извлечь HTTP-заголовки (а также cookie и статус-код) и поместит их в финальный HTTP-ответ, который содержит возвращённое вами значение, отфильтрованное согласно `response_model`.
Вы также можете объявлять параметр `Response` в зависимостях и устанавливать в них заголовки (и cookie).
## Вернуть `Response` напрямую { #return-a-response-directly }
Вы также можете добавить HTTP-заголовки, когда возвращаете `Response` напрямую.
Создайте ответ, как описано в [Вернуть Response напрямую](response-directly.md){.internal-link target=_blank}, и передайте заголовки как дополнительный параметр:
{* ../../docs_src/response_headers/tutorial001.py hl[10:12] *}
/// note | Технические детали
Вы также можете использовать `from starlette.responses import Response` или `from starlette.responses import JSONResponse`.
**FastAPI** предоставляет те же самые `starlette.responses` как `fastapi.responses` — для вашего удобства как разработчика. Но большинство доступных классов ответов поступают напрямую из Starlette.
И поскольку `Response` часто используется для установки заголовков и cookie, **FastAPI** также предоставляет его как `fastapi.Response`.
///
## Пользовательские HTTP-заголовки { #custom-headers }
Помните, что собственные проприетарные заголовки можно добавлять, <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" class="external-link" target="_blank">используя префикс `X-`</a>.
Но если у вас есть пользовательские заголовки, которые вы хотите показывать клиенту в браузере, вам нужно добавить их в настройки CORS (подробнее см. в [CORS (Cross-Origin Resource Sharing)](../tutorial/cors.md){.internal-link target=_blank}), используя параметр `expose_headers`, описанный в <a href="https://www.starlette.dev/middleware/#corsmiddleware" class="external-link" target="_blank">документации Starlette по CORS</a>.
@@ -0,0 +1,107 @@
# HTTP Basic Auth { #http-basic-auth }
Для самых простых случаев можно использовать HTTP Basic Auth.
При HTTP Basic Auth приложение ожидает HTTP-заголовок, который содержит имя пользователя и пароль.
Если его нет, возвращается ошибка HTTP 401 «Unauthorized».
Также возвращается заголовок `WWW-Authenticate` со значением `Basic` и необязательным параметром `realm`.
Это говорит браузеру показать встроенное окно запроса имени пользователя и пароля.
Затем, когда вы вводите эти данные, браузер автоматически отправляет их в заголовке.
## Простой HTTP Basic Auth { #simple-http-basic-auth }
* Импортируйте `HTTPBasic` и `HTTPBasicCredentials`.
* Создайте «схему» `security` с помощью `HTTPBasic`.
* Используйте эту `security` как зависимость в вашей *операции пути*.
* Она возвращает объект типа `HTTPBasicCredentials`:
* Он содержит отправленные `username` и `password`.
{* ../../docs_src/security/tutorial006_an_py39.py hl[4,8,12] *}
Когда вы впервые откроете URL (или нажмёте кнопку «Execute» в документации), браузер попросит ввести имя пользователя и пароль:
<img src="/img/tutorial/security/image12.png">
## Проверка имени пользователя { #check-the-username }
Вот более полный пример.
Используйте зависимость, чтобы проверить, корректны ли имя пользователя и пароль.
Для этого используйте стандартный модуль Python <a href="https://docs.python.org/3/library/secrets.html" class="external-link" target="_blank">`secrets`</a> для проверки имени пользователя и пароля.
`secrets.compare_digest()` должен получать `bytes` или `str`, который содержит только символы ASCII (английские символы). Это значит, что он не будет работать с символами вроде `á`, как в `Sebastián`.
Чтобы это обработать, сначала преобразуем `username` и `password` в `bytes`, закодировав их в UTF-8.
Затем можно использовать `secrets.compare_digest()`, чтобы убедиться, что `credentials.username` равен `"stanleyjobson"`, а `credentials.password``"swordfish"`.
{* ../../docs_src/security/tutorial007_an_py39.py hl[1,12:24] *}
Это было бы похоже на:
```Python
if not (credentials.username == "stanleyjobson") or not (credentials.password == "swordfish"):
# Вернуть ошибку
...
```
Но используя `secrets.compare_digest()`, вы защитите код от атак типа «тайминговая атака» (атака по времени).
### Тайминговые атаки { #timing-attacks }
Что такое «тайминговая атака»?
Представим, что злоумышленники пытаются угадать имя пользователя и пароль.
И они отправляют запрос с именем пользователя `johndoe` и паролем `love123`.
Тогда Python-код в вашем приложении будет эквивалентен чему-то вроде:
```Python
if "johndoe" == "stanleyjobson" and "love123" == "swordfish":
...
```
Но в момент, когда Python сравнит первую `j` в `johndoe` с первой `s` в `stanleyjobson`, он вернёт `False`, потому что уже ясно, что строки не совпадают, решив, что «нет смысла тратить ресурсы на сравнение остальных букв». И ваше приложение ответит «Неверное имя пользователя или пароль».
Затем злоумышленники попробуют имя пользователя `stanleyjobsox` и пароль `love123`.
И ваш код сделает что-то вроде:
```Python
if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish":
...
```
Pythonу придётся сравнить весь общий префикс `stanleyjobso` в `stanleyjobsox` и `stanleyjobson`, прежде чем понять, что строки отличаются. Поэтому на ответ «Неверное имя пользователя или пароль» уйдёт на несколько микросекунд больше.
#### Время ответа помогает злоумышленникам { #the-time-to-answer-helps-the-attackers }
Замечая, что сервер прислал «Неверное имя пользователя или пароль» на несколько микросекунд позже, злоумышленники поймут, что какая-то часть была угадана — начальные буквы верны.
Тогда они могут попробовать снова, зная, что правильнее что-то ближе к `stanleyjobsox`, чем к `johndoe`.
#### «Профессиональная» атака { #a-professional-attack }
Конечно, злоумышленники не будут делать всё это вручную — они напишут программу, возможно, с тысячами или миллионами попыток в секунду. И будут подбирать по одной дополнительной верной букве за раз.
Так за минуты или часы они смогут угадать правильные имя пользователя и пароль — с «помощью» нашего приложения — используя лишь время, затраченное на ответ.
#### Исправление с помощью `secrets.compare_digest()` { #fix-it-with-secrets-compare-digest }
Но в нашем коде мы используем `secrets.compare_digest()`.
Вкратце: сравнение `stanleyjobsox` с `stanleyjobson` займёт столько же времени, сколько и сравнение `johndoe` с `stanleyjobson`. То же относится и к паролю.
Таким образом, используя `secrets.compare_digest()` в коде приложения, вы защитите его от всего этого класса атак на безопасность.
### Возврат ошибки { #return-the-error }
После того как обнаружено, что учётные данные некорректны, верните `HTTPException` со статус-кодом ответа 401 (тем же, что и при отсутствии учётных данных) и добавьте HTTP-заголовок `WWW-Authenticate`, чтобы браузер снова показал окно входа:
{* ../../docs_src/security/tutorial007_an_py39.py hl[26:30] *}
+19
View File
@@ -0,0 +1,19 @@
# Расширенная безопасность { #advanced-security }
## Дополнительные возможности { #additional-features }
Есть дополнительные возможности для работы с безопасностью помимо тех, что описаны в [Учебник — Руководство пользователя: Безопасность](../../tutorial/security/index.md){.internal-link target=_blank}.
/// tip | Совет
Следующие разделы **не обязательно являются «продвинутыми»**.
И возможно, что решение для вашего варианта использования находится в одном из них.
///
## Сначала прочитайте руководство { #read-the-tutorial-first }
В следующих разделах предполагается, что вы уже прочитали основной [Учебник — Руководство пользователя: Безопасность](../../tutorial/security/index.md){.internal-link target=_blank}.
Все они основаны на тех же концепциях, но предоставляют дополнительные возможности.
@@ -0,0 +1,274 @@
# OAuth2 scopes { #oauth2-scopes }
Вы можете использовать OAuth2 scopes (scope - область, рамки) напрямую с **FastAPI** — они интегрированы и работают бесшовно.
Это позволит вам иметь более детальную систему разрешений по стандарту OAuth2, интегрированную в ваше OpenAPI‑приложение (и документацию API).
OAuth2 со scopes — это механизм, который используют многие крупные провайдеры аутентификации: Facebook, Google, GitHub, Microsoft, X (Twitter) и т.д. Они применяют его, чтобы предоставлять конкретные разрешения пользователям и приложениям.
Каждый раз, когда вы «входите через» Facebook, Google, GitHub, Microsoft, X (Twitter), это приложение использует OAuth2 со scopes.
В этом разделе вы увидите, как управлять аутентификацией и авторизацией с теми же OAuth2 scopes в вашем приложении на **FastAPI**.
/// warning | Предупреждение
Это более-менее продвинутый раздел. Если вы только начинаете, можете пропустить его.
Вам не обязательно нужны OAuth2 scopes — аутентификацию и авторизацию можно реализовать любым нужным вам способом.
Но OAuth2 со scopes можно красиво интегрировать в ваш API (через OpenAPI) и документацию API.
Так или иначе, вы все равно будете применять эти scopes или какие-то другие требования безопасности/авторизации, как вам нужно, в вашем коде.
Во многих случаях OAuth2 со scopes может быть избыточным.
Но если вы знаете, что это нужно, или вам просто интересно — продолжайте чтение.
///
## OAuth2 scopes и OpenAPI { #oauth2-scopes-and-openapi }
Спецификация OAuth2 определяет «scopes» как список строк, разделённых пробелами.
Содержимое каждой такой строки может иметь любой формат, но не должно содержать пробелов.
Эти scopes представляют «разрешения».
В OpenAPI (например, в документации API) можно определить «схемы безопасности» (security schemes).
Когда одна из таких схем безопасности использует OAuth2, вы также можете объявлять и использовать scopes.
Каждый «scope» — это просто строка (без пробелов).
Обычно они используются для объявления конкретных разрешений безопасности, например:
- `users:read` или `users:write` — распространённые примеры.
- `instagram_basic` используется Facebook / Instagram.
- `https://www.googleapis.com/auth/drive` используется Google.
/// info | Информация
В OAuth2 «scope» — это просто строка, объявляющая требуемое конкретное разрешение.
Неважно, есть ли там другие символы, такие как `:`, или это URL.
Эти детали зависят от реализации.
Для OAuth2 это просто строки.
///
## Взгляд издалека { #global-view }
Сначала быстро посмотрим, что изменилось по сравнению с примерами из основного раздела **Учебник - Руководство пользователя** — [OAuth2 с паролем (и хешированием), Bearer с JWT-токенами](../../tutorial/security/oauth2-jwt.md){.internal-link target=_blank}. Теперь — с использованием OAuth2 scopes:
{* ../../docs_src/security/tutorial005_an_py310.py hl[5,9,13,47,65,106,108:116,122:126,130:136,141,157] *}
Теперь рассмотрим эти изменения шаг за шагом.
## OAuth2 схема безопасности { #oauth2-security-scheme }
Первое изменение — мы объявляем схему безопасности OAuth2 с двумя доступными scopes: `me` и `items`.
Параметр `scopes` получает `dict`, где каждый scope — это ключ, а описание — значение:
{* ../../docs_src/security/tutorial005_an_py310.py hl[63:66] *}
Так как теперь мы объявляем эти scopes, они появятся в документации API при входе/авторизации.
И вы сможете выбрать, какие scopes вы хотите выдать доступ: `me` и `items`.
Это тот же механизм, когда вы даёте разрешения при входе через Facebook, Google, GitHub и т.д.:
<img src="/img/tutorial/security/image11.png">
## JWT-токены со scopes { #jwt-token-with-scopes }
Теперь измените операцию пути, выдающую токен, чтобы возвращать запрошенные scopes.
Мы всё ещё используем тот же `OAuth2PasswordRequestForm`. Он включает свойство `scopes` с `list` из `str` — каждый scope, полученный в запросе.
И мы возвращаем scopes как часть JWT‑токена.
/// danger | Опасность
Для простоты здесь мы просто добавляем полученные scopes прямо в токен.
Но в вашем приложении, в целях безопасности, следует убедиться, что вы добавляете только те scopes, которые пользователь действительно может иметь, или те, которые вы заранее определили.
///
{* ../../docs_src/security/tutorial005_an_py310.py hl[157] *}
## Объявление scopes в *обработчиках путей* и зависимостях { #declare-scopes-in-path-operations-and-dependencies }
Теперь объявим, что операция пути для `/users/me/items/` требует scope `items`.
Для этого импортируем и используем `Security` из `fastapi`.
Вы можете использовать `Security` для объявления зависимостей (как `Depends`), но `Security` также принимает параметр `scopes` со списком scopes (строк).
В этом случае мы передаём функцию‑зависимость `get_current_active_user` в `Security` (точно так же, как сделали бы с `Depends`).
Но мы также передаём `list` scopes — в данном случае только один scope: `items` (их могло быть больше).
И функция‑зависимость `get_current_active_user` тоже может объявлять подзависимости не только через `Depends`, но и через `Security`, объявляя свою подзависимость (`get_current_user`) и дополнительные требования по scopes.
В данном случае требуется scope `me` (их также могло быть больше одного).
/// note | Примечание
Вам не обязательно добавлять разные scopes в разных местах.
Мы делаем это здесь, чтобы показать, как **FastAPI** обрабатывает scopes, объявленные на разных уровнях.
///
{* ../../docs_src/security/tutorial005_an_py310.py hl[5,141,172] *}
/// info | Технические детали
`Security` на самом деле является подклассом `Depends` и имеет всего один дополнительный параметр, который мы рассмотрим позже.
Но используя `Security` вместо `Depends`, **FastAPI** будет знать, что можно объявлять security scopes, использовать их внутри и документировать API в OpenAPI.
Однако когда вы импортируете `Query`, `Path`, `Depends`, `Security` и другие из `fastapi`, это на самом деле функции, возвращающие специальные классы.
///
## Использование `SecurityScopes` { #use-securityscopes }
Теперь обновим зависимость `get_current_user`.
Именно её используют зависимости выше.
Здесь мы используем ту же схему OAuth2, созданную ранее, объявляя её как зависимость: `oauth2_scheme`.
Поскольку у этой функции‑зависимости нет собственных требований по scopes, мы можем использовать `Depends` с `oauth2_scheme` — нам не нужно использовать `Security`, если не требуется указывать security scopes.
Мы также объявляем специальный параметр типа `SecurityScopes`, импортированный из `fastapi.security`.
Класс `SecurityScopes` похож на `Request` (через `Request` мы получали сам объект запроса).
{* ../../docs_src/security/tutorial005_an_py310.py hl[9,106] *}
## Использование `scopes` { #use-the-scopes }
Параметр `security_scopes` будет типа `SecurityScopes`.
У него есть свойство `scopes` со списком, содержащим все scopes, требуемые им самим и всеми зависимостями, использующими его как подзависимость. То есть всеми «зависящими»… это может звучать запутанно, ниже есть дополнительное объяснение.
Объект `security_scopes` (класс `SecurityScopes`) также предоставляет атрибут `scope_str` — это одна строка с этими scopes, разделёнными пробелами (мы будем её использовать).
Мы создаём `HTTPException`, который можем переиспользовать (`raise`) в нескольких местах.
В этом исключении мы включаем требуемые scopes (если есть) в виде строки, разделённой пробелами (используя `scope_str`). Эту строку со scopes мы помещаем в HTTP‑заголовок `WWW-Authenticate` (это часть спецификации).
{* ../../docs_src/security/tutorial005_an_py310.py hl[106,108:116] *}
## Проверка `username` и формата данных { #verify-the-username-and-data-shape }
Мы проверяем, что получили `username`, и извлекаем scopes.
Затем валидируем эти данные с помощью Pydantic‑модели (перехватывая исключение `ValidationError`), и если возникает ошибка при чтении JWT‑токена или при валидации данных с Pydantic, мы вызываем `HTTPException`, созданное ранее.
Для этого мы обновляем Pydantic‑модель `TokenData`, добавляя новое свойство `scopes`.
Валидируя данные с помощью Pydantic, мы можем удостовериться, что у нас, например, именно `list` из `str` со scopes и `str` с `username`.
А не, скажем, `dict` или что‑то ещё — ведь это могло бы где‑то позже сломать приложение и создать риск для безопасности.
Мы также проверяем, что существует пользователь с таким именем, и если нет — вызываем то же исключение, созданное ранее.
{* ../../docs_src/security/tutorial005_an_py310.py hl[47,117:129] *}
## Проверка `scopes` { #verify-the-scopes }
Теперь проверяем, что все требуемые scopes — этой зависимостью и всеми зависящими (включая операции пути) — присутствуют среди scopes, предоставленных в полученном токене, иначе вызываем `HTTPException`.
Для этого используем `security_scopes.scopes`, содержащий `list` со всеми этими scopes как `str`.
{* ../../docs_src/security/tutorial005_an_py310.py hl[130:136] *}
## Дерево зависимостей и scopes { #dependency-tree-and-scopes }
Ещё раз рассмотрим дерево зависимостей и scopes.
Так как у зависимости `get_current_active_user` есть подзависимость `get_current_user`, scope `"me"`, объявленный в `get_current_active_user`, будет включён в список требуемых scopes в `security_scopes.scopes`, передаваемый в `get_current_user`.
Сама операция пути тоже объявляет scope — `"items"`, поэтому он также будет в списке `security_scopes.scopes`, передаваемом в `get_current_user`.
Иерархия зависимостей и scopes выглядит так:
- Операция пути `read_own_items`:
- Запрашивает scopes `["items"]` с зависимостью:
- `get_current_active_user`:
- Функция‑зависимость `get_current_active_user`:
- Запрашивает scopes `["me"]` с зависимостью:
- `get_current_user`:
- Функция‑зависимость `get_current_user`:
- Собственных scopes не запрашивает.
- Имеет зависимость, использующую `oauth2_scheme`.
- Имеет параметр `security_scopes` типа `SecurityScopes`:
- Этот параметр `security_scopes` имеет свойство `scopes` с `list`, содержащим все объявленные выше scopes, то есть:
- `security_scopes.scopes` будет содержать `["me", "items"]` для операции пути `read_own_items`.
- `security_scopes.scopes` будет содержать `["me"]` для операции пути `read_users_me`, потому что он объявлен в зависимости `get_current_active_user`.
- `security_scopes.scopes` будет содержать `[]` (ничего) для операции пути `read_system_status`, потому что там не объявлялся `Security` со `scopes`, и его зависимость `get_current_user` тоже не объявляет `scopes`.
/// tip | Совет
Важный и «магический» момент здесь в том, что `get_current_user` будет иметь разный список `scopes` для проверки для каждой операции пути.
Всё это зависит от `scopes`, объявленных в каждой операции пути и в каждой зависимости в дереве зависимостей конкретной операции пути.
///
## Больше деталей о `SecurityScopes` { #more-details-about-securityscopes }
Вы можете использовать `SecurityScopes` в любой точке и в нескольких местах — необязательно в «корневой» зависимости.
Он всегда будет содержать security scopes, объявленные в текущих зависимостях `Security`, и всеми зависящими — для этой конкретной операции пути и этого конкретного дерева зависимостей.
Поскольку `SecurityScopes` будет содержать все scopes, объявленные зависящими, вы можете использовать его, чтобы централизованно проверять наличие требуемых scopes в токене в одной функции‑зависимости, а затем объявлять разные требования по scopes в разных операциях пути.
Они будут проверяться независимо для каждой операции пути.
## Проверим это { #check-it }
Откройте документацию API — вы сможете аутентифицироваться и указать, какие scopes вы хотите авторизовать.
<img src="/img/tutorial/security/image11.png">
Если вы не выберете ни один scope, вы будете «аутентифицированы», но при попытке доступа к `/users/me/` или `/users/me/items/` получите ошибку о недостаточных разрешениях. При этом доступ к `/status/` будет возможен.
Если вы выберете scope `me`, но не `items`, вы сможете получить доступ к `/users/me/`, но не к `/users/me/items/`.
Так и будет происходить со сторонним приложением, которое попытается обратиться к одной из этих операций пути с токеном, предоставленным пользователем, — в зависимости от того, сколько разрешений пользователь дал приложению.
## О сторонних интеграциях { #about-third-party-integrations }
В этом примере мы используем OAuth2 «password flow» (аутентификация по паролю).
Это уместно, когда мы входим в наше собственное приложение, вероятно, с нашим собственным фронтендом.
Мы можем ему доверять при получении `username` и `password`, потому что он под нашим контролем.
Но если вы создаёте OAuth2‑приложение, к которому будут подключаться другие (т.е. вы строите провайдера аутентификации наподобие Facebook, Google, GitHub и т.п.), вам следует использовать один из других «flows».
Самый распространённый — «implicit flow».
Самый безопасный — «code flow», но он сложнее в реализации, так как требует больше шагов. Из‑за сложности многие провайдеры в итоге рекомендуют «implicit flow».
/// note | Примечание
Часто каждый провайдер аутентификации называет свои «flows» по‑разному — как часть бренда.
Но в итоге они реализуют один и тот же стандарт OAuth2.
///
FastAPI включает утилиты для всех этих OAuth2‑flows в `fastapi.security.oauth2`.
## `Security` в параметре `dependencies` декоратора { #security-in-decorator-dependencies }
Точно так же, как вы можете определить `list` из `Depends` в параметре `dependencies` декоратора (см. [Зависимости в декораторах операции пути](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), вы можете использовать там и `Security` со `scopes`.
+346
View File
@@ -0,0 +1,346 @@
# Настройки и переменные окружения { #settings-and-environment-variables }
Во многих случаях вашему приложению могут понадобиться внешние настройки или конфигурации, например секретные ключи, учетные данные для базы данных, учетные данные для email‑сервисов и т.д.
Большинство таких настроек являются изменяемыми (могут меняться), например URL базы данных. И многие из них могут быть «чувствительными», например секреты.
По этой причине обычно их передают через переменные окружения, которые считываются приложением.
/// tip | Совет
Чтобы понять, что такое переменные окружения, вы можете прочитать [Переменные окружения](../environment-variables.md){.internal-link target=_blank}.
///
## Типы и валидация { #types-and-validation }
Переменные окружения могут содержать только текстовые строки, так как они внешние по отношению к Python и должны быть совместимы с другими программами и остальной системой (и даже с разными операционными системами, такими как Linux, Windows, macOS).
Это означает, что любое значение, прочитанное в Python из переменной окружения, будет `str`, а любые преобразования к другим типам или любая валидация должны выполняться в коде.
## Pydantic `Settings` { #pydantic-settings }
К счастью, Pydantic предоставляет отличную утилиту для работы с этими настройками, поступающими из переменных окружения, — <a href="https://docs.pydantic.dev/latest/concepts/pydantic_settings/" class="external-link" target="_blank">Pydantic: управление настройками</a>.
### Установка `pydantic-settings` { #install-pydantic-settings }
Сначала убедитесь, что вы создали [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активировали его, а затем установили пакет `pydantic-settings`:
<div class="termy">
```console
$ pip install pydantic-settings
---> 100%
```
</div>
Он также включен при установке набора `all` с:
<div class="termy">
```console
$ pip install "fastapi[all]"
---> 100%
```
</div>
/// info | Информация
В Pydantic v1 он входил в основной пакет. Теперь он распространяется как отдельный пакет, чтобы вы могли установить его только при необходимости.
///
### Создание объекта `Settings` { #create-the-settings-object }
Импортируйте `BaseSettings` из Pydantic и создайте подкласс, очень похожий на Pydantic‑модель.
Аналогично Pydantic‑моделям, вы объявляете атрибуты класса с аннотациями типов и, при необходимости, значениями по умолчанию.
Вы можете использовать все те же возможности валидации и инструменты, что и для Pydantic‑моделей, например разные типы данных и дополнительную валидацию через `Field()`.
//// tab | Pydantic v2
{* ../../docs_src/settings/tutorial001.py hl[2,5:8,11] *}
////
//// tab | Pydantic v1
/// info | Информация
В Pydantic v1 вы бы импортировали `BaseSettings` напрямую из `pydantic`, а не из `pydantic_settings`.
///
{* ../../docs_src/settings/tutorial001_pv1.py hl[2,5:8,11] *}
////
/// tip | Совет
Если вам нужно что-то быстро скопировать и вставить, не используйте этот пример — воспользуйтесь последним ниже.
///
Затем, когда вы создаете экземпляр этого класса `Settings` (в нашем случае объект `settings`), Pydantic прочитает переменные окружения регистронезависимо, то есть переменная в верхнем регистре `APP_NAME` будет прочитана для атрибута `app_name`.
Далее он преобразует и провалидирует данные. Поэтому при использовании объекта `settings` вы получите данные тех типов, которые объявили (например, `items_per_user` будет `int`).
### Использование `settings` { #use-the-settings }
Затем вы можете использовать новый объект `settings` в вашем приложении:
{* ../../docs_src/settings/tutorial001.py hl[18:20] *}
### Запуск сервера { #run-the-server }
Далее вы можете запустить сервер, передав конфигурации через переменные окружения. Например, можно задать `ADMIN_EMAIL` и `APP_NAME` так:
<div class="termy">
```console
$ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.py
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
/// tip | Совет
Чтобы задать несколько переменных окружения для одной команды, просто разделяйте их пробелами и укажите все перед командой.
///
Тогда параметр `admin_email` будет установлен в `"deadpool@example.com"`.
`app_name` будет `"ChimichangApp"`.
А `items_per_user` сохранит значение по умолчанию `50`.
## Настройки в другом модуле { #settings-in-another-module }
Вы можете вынести эти настройки в другой модуль, как показано в разделе [Большие приложения — несколько файлов](../tutorial/bigger-applications.md){.internal-link target=_blank}.
Например, у вас может быть файл `config.py` со следующим содержимым:
{* ../../docs_src/settings/app01/config.py *}
А затем использовать его в файле `main.py`:
{* ../../docs_src/settings/app01/main.py hl[3,11:13] *}
/// tip | Совет
Вам также понадобится файл `__init__.py`, как в разделе [Большие приложения — несколько файлов](../tutorial/bigger-applications.md){.internal-link target=_blank}.
///
## Настройки как зависимость { #settings-in-a-dependency }
Иногда может быть полезно предоставлять настройки через зависимость, вместо глобального объекта `settings`, используемого повсюду.
Это особенно удобно при тестировании, так как очень легко переопределить зависимость своими настройками.
### Файл конфигурации { #the-config-file }
Продолжая предыдущий пример, ваш файл `config.py` может выглядеть так:
{* ../../docs_src/settings/app02/config.py hl[10] *}
Обратите внимание, что теперь мы не создаем экземпляр по умолчанию `settings = Settings()`.
### Основной файл приложения { #the-main-app-file }
Теперь мы создаем зависимость, которая возвращает новый `config.Settings()`.
{* ../../docs_src/settings/app02_an_py39/main.py hl[6,12:13] *}
/// tip | Совет
Скоро мы обсудим `@lru_cache`.
Пока можно считать, что `get_settings()` — это обычная функция.
///
Затем мы можем запросить ее в *функции-обработчике пути* как зависимость и использовать там, где нужно.
{* ../../docs_src/settings/app02_an_py39/main.py hl[17,19:21] *}
### Настройки и тестирование { #settings-and-testing }
Далее будет очень просто предоставить другой объект настроек во время тестирования, создав переопределение зависимости для `get_settings`:
{* ../../docs_src/settings/app02/test_main.py hl[9:10,13,21] *}
В переопределении зависимости мы задаем новое значение `admin_email` при создании нового объекта `Settings`, а затем возвращаем этот новый объект.
После этого можно протестировать, что он используется.
## Чтение файла `.env` { #reading-a-env-file }
Если у вас много настроек, которые могут часто меняться, возможно в разных окружениях, может быть удобно поместить их в файл и читать оттуда как переменные окружения.
Эта практика достаточно распространена и имеет название: такие переменные окружения обычно размещают в файле `.env`, а сам файл называют «dotenv».
/// tip | Совет
Файл, начинающийся с точки (`.`), является скрытым в системах, подобных Unix, таких как Linux и macOS.
Но файл dotenv не обязательно должен иметь именно такое имя.
///
Pydantic поддерживает чтение таких файлов с помощью внешней библиотеки. Подробнее вы можете прочитать здесь: <a href="https://docs.pydantic.dev/latest/concepts/pydantic_settings/#dotenv-env-support" class="external-link" target="_blank">Pydantic Settings: поддержка Dotenv (.env)</a>.
/// tip | Совет
Чтобы это работало, вам нужно `pip install python-dotenv`.
///
### Файл `.env` { #the-env-file }
У вас может быть файл `.env` со следующим содержимым:
```bash
ADMIN_EMAIL="deadpool@example.com"
APP_NAME="ChimichangApp"
```
### Чтение настроек из `.env` { #read-settings-from-env }
Затем обновите ваш `config.py` так:
//// tab | Pydantic v2
{* ../../docs_src/settings/app03_an/config.py hl[9] *}
/// tip | Совет
Атрибут `model_config` используется только для конфигурации Pydantic. Подробнее см. <a href="https://docs.pydantic.dev/latest/concepts/config/" class="external-link" target="_blank">Pydantic: Concepts: Configuration</a>.
///
////
//// tab | Pydantic v1
{* ../../docs_src/settings/app03_an/config_pv1.py hl[9:10] *}
/// tip | Совет
Класс `Config` используется только для конфигурации Pydantic. Подробнее см. <a href="https://docs.pydantic.dev/1.10/usage/model_config/" class="external-link" target="_blank">Pydantic Model Config</a>.
///
////
/// info | Информация
В Pydantic версии 1 конфигурация задавалась во внутреннем классе `Config`, в Pydantic версии 2 — в атрибуте `model_config`. Этот атрибут принимает `dict`, и чтобы получить автозавершение и ошибки «на лету», вы можете импортировать и использовать `SettingsConfigDict` для описания этого `dict`.
///
Здесь мы задаем параметр конфигурации `env_file` внутри вашего класса Pydantic `Settings` и устанавливаем значение равным имени файла dotenv, который хотим использовать.
### Создание `Settings` только один раз с помощью `lru_cache` { #creating-the-settings-only-once-with-lru-cache }
Чтение файла с диска обычно затратная (медленная) операция, поэтому, вероятно, вы захотите сделать это один раз и затем переиспользовать один и тот же объект настроек, а не читать файл при каждом запросе.
Но каждый раз, когда мы делаем:
```Python
Settings()
```
создается новый объект `Settings`, и при создании он снова считывает файл `.env`.
Если бы функция зависимости была такой:
```Python
def get_settings():
return Settings()
```
мы бы создавали этот объект для каждого запроса и читали файл `.env` на каждый запрос. ⚠️
Но так как мы используем декоратор `@lru_cache` сверху, объект `Settings` будет создан только один раз — при первом вызове. ✔️
{* ../../docs_src/settings/app03_an_py39/main.py hl[1,11] *}
Затем при любых последующих вызовах `get_settings()` в зависимостях для следующих запросов, вместо выполнения внутреннего кода `get_settings()` и создания нового объекта `Settings`, будет возвращаться тот же объект, что был возвращен при первом вызове, снова и снова.
#### Технические детали `lru_cache` { #lru-cache-technical-details }
`@lru_cache` модифицирует декорируемую функцию так, что она возвращает то же значение, что и в первый раз, вместо повторного вычисления, то есть вместо выполнения кода функции каждый раз.
Таким образом, функция под декоратором будет выполнена один раз для каждой комбинации аргументов. Затем значения, возвращенные для каждой из этих комбинаций, будут использоваться снова и снова при вызове функции с точно такой же комбинацией аргументов.
Например, если у вас есть функция:
```Python
@lru_cache
def say_hi(name: str, salutation: str = "Ms."):
return f"Hello {salutation} {name}"
```
ваша программа может выполняться так:
```mermaid
sequenceDiagram
participant code as Code
participant function as say_hi()
participant execute as Execute function
rect rgba(0, 255, 0, .1)
code ->> function: say_hi(name="Camila")
function ->> execute: execute function code
execute ->> code: return the result
end
rect rgba(0, 255, 255, .1)
code ->> function: say_hi(name="Camila")
function ->> code: return stored result
end
rect rgba(0, 255, 0, .1)
code ->> function: say_hi(name="Rick")
function ->> execute: execute function code
execute ->> code: return the result
end
rect rgba(0, 255, 0, .1)
code ->> function: say_hi(name="Rick", salutation="Mr.")
function ->> execute: execute function code
execute ->> code: return the result
end
rect rgba(0, 255, 255, .1)
code ->> function: say_hi(name="Rick")
function ->> code: return stored result
end
rect rgba(0, 255, 255, .1)
code ->> function: say_hi(name="Camila")
function ->> code: return stored result
end
```
В случае нашей зависимости `get_settings()` функция вообще не принимает аргументов, поэтому она всегда возвращает одно и то же значение.
Таким образом, она ведет себя почти как глобальная переменная. Но так как используется функция‑зависимость, мы можем легко переопределить ее для тестирования.
`@lru_cache` — часть `functools`, что входит в стандартную библиотеку Python. Подробнее можно прочитать в <a href="https://docs.python.org/3/library/functools.html#functools.lru_cache" class="external-link" target="_blank">документации Python по `@lru_cache`</a>.
## Итоги { #recap }
Вы можете использовать Pydantic Settings для управления настройками и конфигурациями вашего приложения с полной мощью Pydantic‑моделей.
* Используя зависимость, вы упрощаете тестирование.
* Можно использовать файлы `.env`.
* `@lru_cache` позволяет не читать файл dotenv снова и снова для каждого запроса, при этом давая возможность переопределять его во время тестирования.
+67
View File
@@ -0,0 +1,67 @@
# Подприложения — Mounts (монтирование) { #sub-applications-mounts }
Если вам нужны два независимых приложения FastAPI, каждое со своим собственным OpenAPI и собственными интерфейсами документации, вы можете иметь основное приложение и «смонтировать» одно (или несколько) подприложений.
## Монтирование приложения **FastAPI** { #mounting-a-fastapi-application }
«Монтирование» означает добавление полностью независимого приложения по конкретному пути; далее оно будет обрабатывать всё под этим путём, используя объявленные в подприложении _операции пути_.
### Приложение верхнего уровня { #top-level-application }
Сначала создайте основное, верхнего уровня, приложение **FastAPI** и его *операции пути*:
{* ../../docs_src/sub_applications/tutorial001.py hl[3, 6:8] *}
### Подприложение { #sub-application }
Затем создайте подприложение и его *операции пути*.
Это подприложение — обычное стандартное приложение FastAPI, но именно оно будет «смонтировано»:
{* ../../docs_src/sub_applications/tutorial001.py hl[11, 14:16] *}
### Смонтируйте подприложение { #mount-the-sub-application }
В вашем приложении верхнего уровня, `app`, смонтируйте подприложение `subapi`.
В этом случае оно будет смонтировано по пути `/subapi`:
{* ../../docs_src/sub_applications/tutorial001.py hl[11, 19] *}
### Проверьте автоматическую документацию API { #check-the-automatic-api-docs }
Теперь запустите команду `fastapi` с вашим файлом:
<div class="termy">
```console
$ fastapi dev main.py
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
И откройте документацию по адресу <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
Вы увидите автоматическую документацию API для основного приложения, включающую только его собственные _операции пути_:
<img src="/img/tutorial/sub-applications/image01.png">
Затем откройте документацию для подприложения по адресу <a href="http://127.0.0.1:8000/subapi/docs" class="external-link" target="_blank">http://127.0.0.1:8000/subapi/docs</a>.
Вы увидите автоматическую документацию API для подприложения, включающую только его собственные _операции пути_, все под корректным префиксом подпути `/subapi`:
<img src="/img/tutorial/sub-applications/image02.png">
Если вы попробуете взаимодействовать с любым из двух интерфейсов, всё будет работать корректно, потому что браузер сможет обращаться к каждому конкретному приложению и подприложению.
### Технические подробности: `root_path` { #technical-details-root-path }
Когда вы монтируете подприложение, как описано выше, FastAPI позаботится о передаче пути монтирования для подприложения, используя механизм из спецификации ASGI под названием `root_path`.
Таким образом подприложение будет знать, что для интерфейса документации нужно использовать этот префикс пути.
У подприложения также могут быть свои собственные смонтированные подприложения, и всё будет работать корректно, потому что FastAPI автоматически обрабатывает все эти `root_path`.
Вы узнаете больше о `root_path` и о том, как использовать его явно, в разделе [За прокси](behind-a-proxy.md){.internal-link target=_blank}.
+126
View File
@@ -0,0 +1,126 @@
# Шаблоны { #templates }
Вы можете использовать любой шаблонизатор вместе с **FastAPI**.
Часто выбирают Jinja2 — тот же, что используется во Flask и других инструментах.
Есть утилиты для простой настройки, которые вы можете использовать прямо в своем приложении **FastAPI** (предоставляются Starlette).
## Установка зависимостей { #install-dependencies }
Убедитесь, что вы создали [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активировали его и установили `jinja2`:
<div class="termy">
```console
$ pip install jinja2
---> 100%
```
</div>
## Использование `Jinja2Templates` { #using-jinja2templates }
- Импортируйте `Jinja2Templates`.
- Создайте объект `templates`, который сможете переиспользовать позже.
- Объявите параметр `Request` в *операции пути*, которая будет возвращать шаблон.
- Используйте созданный `templates`, чтобы отрендерить и вернуть `TemplateResponse`; передайте имя шаблона, объект `request` и словарь «context» с парами ключ-значение для использования внутри шаблона Jinja2.
{* ../../docs_src/templates/tutorial001.py hl[4,11,15:18] *}
/// note | Примечание
До FastAPI 0.108.0, Starlette 0.29.0, `name` был первым параметром.
Также раньше, в предыдущих версиях, объект `request` передавался как часть пар ключ-значение в контексте для Jinja2.
///
/// tip | Совет
Если указать `response_class=HTMLResponse`, интерфейс документации сможет определить, что ответ будет в формате HTML.
///
/// note | Технические детали
Можно также использовать `from starlette.templating import Jinja2Templates`.
**FastAPI** предоставляет тот же `starlette.templating` как `fastapi.templating` просто для удобства разработчика. Но большинство доступных ответов приходят напрямую из Starlette. Так же и с `Request` и `StaticFiles`.
///
## Написание шаблонов { #writing-templates }
Затем вы можете создать шаблон в `templates/item.html`, например:
```jinja hl_lines="7"
{!../../docs_src/templates/templates/item.html!}
```
### Значения контекста шаблона { #template-context-values }
В HTML, который содержит:
{% raw %}
```jinja
Item ID: {{ id }}
```
{% endraw %}
...будет показан `id`, взятый из переданного вами «context» `dict`:
```Python
{"id": id}
```
Например, для ID `42` это отрендерится как:
```html
Item ID: 42
```
### Аргументы `url_for` в шаблоне { #template-url-for-arguments }
Вы также можете использовать `url_for()` внутри шаблона — он принимает те же аргументы, что использовались бы вашей *функцией-обработчиком пути*.
Таким образом, фрагмент:
{% raw %}
```jinja
<a href="{{ url_for('read_item', id=id) }}">
```
{% endraw %}
...сгенерирует ссылку на тот же URL, который обрабатывается *функцией-обработчиком пути* `read_item(id=id)`.
Например, для ID `42` это отрендерится как:
```html
<a href="/items/42">
```
## Шаблоны и статические файлы { #templates-and-static-files }
Вы также можете использовать `url_for()` внутри шаблона, например, с `StaticFiles`, которые вы монтировали с `name="static"`.
```jinja hl_lines="4"
{!../../docs_src/templates/templates/item.html!}
```
В этом примере будет создана ссылка на CSS-файл `static/styles.css` с помощью:
```CSS hl_lines="4"
{!../../docs_src/templates/static/styles.css!}
```
И, так как вы используете `StaticFiles`, этот CSS-файл будет автоматически «отдаваться» вашим приложением **FastAPI** по URL `/static/styles.css`.
## Подробнее { #more-details }
Больше подробностей, включая то, как тестировать шаблоны, смотрите в <a href="https://www.starlette.dev/templates/" class="external-link" target="_blank">документации Starlette по шаблонам</a>.
@@ -0,0 +1,53 @@
# Тестирование зависимостей с переопределениями { #testing-dependencies-with-overrides }
## Переопределение зависимостей во время тестирования { #overriding-dependencies-during-testing }
Есть сценарии, когда может понадобиться переопределить зависимость во время тестирования.
Вы не хотите, чтобы исходная зависимость выполнялась (и любые её подзависимости тоже).
Вместо этого вы хотите предоставить другую зависимость, которая будет использоваться только во время тестов (возможно, только в некоторых конкретных тестах) и будет возвращать значение, которое можно использовать везде, где использовалось значение исходной зависимости.
### Варианты использования: внешний сервис { #use-cases-external-service }
Пример: у вас есть внешний провайдер аутентификации, к которому нужно обращаться.
Вы отправляете ему токен, а он возвращает аутентифицированного пользователя.
Такой провайдер может брать плату за каждый запрос, и его вызов может занимать больше времени, чем использование фиксированного мок-пользователя для тестов.
Вероятно, вы захотите протестировать внешний провайдер один раз, но не обязательно вызывать его для каждого запускаемого теста.
В таком случае вы можете переопределить зависимость, которая обращается к этому провайдеру, и использовать собственную зависимость, возвращающую мок-пользователя, только для ваших тестов.
### Используйте атрибут `app.dependency_overrides` { #use-the-app-dependency-overrides-attribute }
Для таких случаев у вашего приложения **FastAPI** есть атрибут `app.dependency_overrides`, это простой `dict`.
Чтобы переопределить зависимость для тестирования, укажите в качестве ключа исходную зависимость (функцию), а в качестве значения — ваше переопределение зависимости (другую функцию).
Тогда **FastAPI** будет вызывать это переопределение вместо исходной зависимости.
{* ../../docs_src/dependency_testing/tutorial001_an_py310.py hl[26:27,30] *}
/// tip | Совет
Вы можете задать переопределение для зависимости, используемой в любом месте вашего приложения **FastAPI**.
Исходная зависимость может использоваться в функции-обработчике пути, в декораторе операции пути (когда вы не используете возвращаемое значение), в вызове `.include_router()` и т.д.
FastAPI всё равно сможет её переопределить.
///
Затем вы можете сбросить переопределения (удалить их), установив `app.dependency_overrides` в пустой `dict`:
```Python
app.dependency_overrides = {}
```
/// tip | Совет
Если вы хотите переопределять зависимость только во время некоторых тестов, задайте переопределение в начале теста (внутри функции теста) и сбросьте его в конце (в конце функции теста).
///
+12
View File
@@ -0,0 +1,12 @@
# Тестирование событий: lifespan и startup - shutdown { #testing-events-lifespan-and-startup-shutdown }
Если вам нужно, чтобы `lifespan` выполнялся в ваших тестах, вы можете использовать `TestClient` вместе с оператором `with`:
{* ../../docs_src/app_testing/tutorial004.py hl[9:15,18,27:28,30:32,41:43] *}
Вы можете узнать больше подробностей в статье [Запуск lifespan в тестах на официальном сайте документации Starlette.](https://www.starlette.dev/lifespan/#running-lifespan-in-tests)
Для устаревших событий `startup` и `shutdown` вы можете использовать `TestClient` следующим образом:
{* ../../docs_src/app_testing/tutorial003.py hl[9:12,20:24] *}
@@ -0,0 +1,13 @@
# Тестирование WebSocket { #testing-websockets }
Вы можете использовать тот же `TestClient` для тестирования WebSocket.
Для этого используйте `TestClient` с менеджером контекста `with`, подключаясь к WebSocket:
{* ../../docs_src/app_testing/tutorial002.py hl[27:31] *}
/// note | Примечание
Подробности смотрите в документации Starlette по <a href="https://www.starlette.dev/testclient/#testing-websocket-sessions" class="external-link" target="_blank">тестированию WebSocket</a>.
///
@@ -0,0 +1,56 @@
# Прямое использование Request { #using-the-request-directly }
До этого вы объявляли нужные части HTTP-запроса вместе с их типами.
Извлекая данные из:
* пути (как параметров),
* HTTP-заголовков,
* Cookie,
* и т.д.
Тем самым **FastAPI** валидирует эти данные, преобразует их и автоматически генерирует документацию для вашего API.
Но бывают ситуации, когда нужно обратиться к объекту `Request` напрямую.
## Подробности об объекте `Request` { #details-about-the-request-object }
Так как под капотом **FastAPI** — это **Starlette** с дополнительным слоем инструментов, вы можете при необходимости напрямую использовать объект <a href="https://www.starlette.dev/requests/" class="external-link" target="_blank">`Request`</a> из Starlette.
Это также означает, что если вы получаете данные напрямую из объекта `Request` (например, читаете тело запроса), то они не будут валидироваться, конвертироваться или документироваться (с OpenAPI, для автоматического пользовательского интерфейса API) средствами FastAPI.
При этом любой другой параметр, объявленный обычным образом (например, тело запроса с Pydantic-моделью), по-прежнему будет валидироваться, конвертироваться, аннотироваться и т.д.
Но есть конкретные случаи, когда полезно получить объект `Request`.
## Используйте объект `Request` напрямую { #use-the-request-object-directly }
Представим, что вы хотите получить IP-адрес/хост клиента внутри вашей *функции-обработчика пути*.
Для этого нужно обратиться к запросу напрямую.
{* ../../docs_src/using_request_directly/tutorial001.py hl[1,7:8] *}
Если объявить параметр *функции-обработчика пути* с типом `Request`, **FastAPI** поймёт, что нужно передать объект `Request` в этот параметр.
/// tip | Совет
Обратите внимание, что в этом примере мы объявляем path-параметр вместе с параметром `Request`.
Таким образом, path-параметр будет извлечён, валидирован, преобразован к указанному типу и задокументирован в OpenAPI.
Точно так же вы можете объявлять любые другие параметры как обычно и, дополнительно, получать `Request`.
///
## Документация по `Request` { #request-documentation }
Подробнее об <a href="https://www.starlette.dev/requests/" class="external-link" target="_blank">объекте `Request` на официальном сайте документации Starlette</a>.
/// note | Технические детали
Вы также можете использовать `from starlette.requests import Request`.
**FastAPI** предоставляет его напрямую для удобства разработчика, но сам объект приходит из Starlette.
///
+186
View File
@@ -0,0 +1,186 @@
# Веб-сокеты { #websockets }
Вы можете использовать <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API" class="external-link" target="_blank">веб-сокеты</a> в **FastAPI**.
## Установка `websockets` { #install-websockets }
Убедитесь, что вы создали [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активировали его и установили `websockets` (библиотека Python, упрощающая работу с протоколом "WebSocket"):
<div class="termy">
```console
$ pip install websockets
---> 100%
```
</div>
## Клиент WebSockets { #websockets-client }
### В продакшн { #in-production }
В продакшн у вас, вероятно, есть фронтенд, созданный с помощью современного фреймворка вроде React, Vue.js или Angular.
И для взаимодействия с бекендом по WebSocket вы, скорее всего, будете использовать инструменты вашего фронтенда.
Также у вас может быть нативное мобильное приложение, которое напрямую, нативным кодом, взаимодействует с вашим WebSocket-бекендом.
Либо у вас может быть любой другой способ взаимодействия с WebSocket-эндпоинтом.
---
Но для этого примера мы воспользуемся очень простым HTML‑документом с небольшим JavaScript, всё внутри одной длинной строки.
Конечно же, это неоптимально, и вы бы не использовали это в продакшн.
В продакшн у вас был бы один из вариантов выше.
Для примера нам нужен наиболее простой способ, который позволит сосредоточиться на серверной части веб‑сокетов и получить рабочий код:
{* ../../docs_src/websockets/tutorial001.py hl[2,6:38,41:43] *}
## Создание `websocket` { #create-a-websocket }
Создайте `websocket` в своем **FastAPI** приложении:
{* ../../docs_src/websockets/tutorial001.py hl[1,46:47] *}
/// note | Технические детали
Вы также можете использовать `from starlette.websockets import WebSocket`.
**FastAPI** напрямую предоставляет тот же самый `WebSocket` просто для удобства. На самом деле это `WebSocket` из Starlette.
///
## Ожидание и отправка сообщений { #await-for-messages-and-send-messages }
Через эндпоинт веб-сокета вы можете получать и отправлять сообщения.
{* ../../docs_src/websockets/tutorial001.py hl[48:52] *}
Вы можете получать и отправлять двоичные, текстовые и JSON данные.
## Проверка в действии { #try-it }
Если ваш файл называется `main.py`, то запустите приложение командой:
<div class="termy">
```console
$ fastapi dev main.py
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
Откройте браузер по адресу <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>.
Вы увидите следующую простенькую страницу:
<img src="/img/tutorial/websockets/image01.png">
Вы можете набирать сообщения в поле ввода и отправлять их:
<img src="/img/tutorial/websockets/image02.png">
И ваше **FastAPI** приложение с веб-сокетами ответит:
<img src="/img/tutorial/websockets/image03.png">
Вы можете отправлять и получать множество сообщений:
<img src="/img/tutorial/websockets/image04.png">
И все они будут использовать одно и то же веб-сокет соединение.
## Использование `Depends` и не только { #using-depends-and-others }
Вы можете импортировать из `fastapi` и использовать в эндпоинте вебсокета:
* `Depends`
* `Security`
* `Cookie`
* `Header`
* `Path`
* `Query`
Они работают так же, как и в других FastAPI эндпоинтах/*операциях пути*:
{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *}
/// info | Примечание
В веб-сокете вызывать `HTTPException` не имеет смысла. Вместо этого нужно использовать `WebSocketException`.
Закрывающий статус код можно использовать из <a href="https://tools.ietf.org/html/rfc6455#section-7.4.1" class="external-link" target="_blank">valid codes defined in the specification</a>.
///
### Веб-сокеты с зависимостями: проверка в действии { #try-the-websockets-with-dependencies }
Если ваш файл называется `main.py`, то запустите приложение командой:
<div class="termy">
```console
$ fastapi dev main.py
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
Откройте браузер по адресу <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>.
Там вы можете задать:
* "Item ID", используемый в пути.
* "Token", используемый как query-параметр.
/// tip | Подсказка
Обратите внимание, что query-параметр `token` будет обработан в зависимости.
///
Теперь вы можете подключиться к веб-сокету и начинать отправку и получение сообщений:
<img src="/img/tutorial/websockets/image05.png">
## Обработка отключений и работа с несколькими клиентами { #handling-disconnections-and-multiple-clients }
Если веб-сокет соединение закрыто, то `await websocket.receive_text()` вызовет исключение `WebSocketDisconnect`, которое можно поймать и обработать как в этом примере:
{* ../../docs_src/websockets/tutorial003_py39.py hl[79:81] *}
Чтобы воспроизвести пример:
* Откройте приложение в нескольких вкладках браузера.
* Отправьте из них сообщения.
* Затем закройте одну из вкладок.
Это вызовет исключение `WebSocketDisconnect`, и все остальные клиенты получат следующее сообщение:
```
Client #1596980209979 left the chat
```
/// tip | Подсказка
Приложение выше - это всего лишь простой минимальный пример, демонстрирующий обработку и передачу сообщений нескольким веб-сокет соединениям.
Но имейте в виду, что это будет работать только в одном процессе и только пока он активен, так как всё обрабатывается в простом списке в оперативной памяти.
Если нужно что-то легко интегрируемое с FastAPI, но более надежное и с поддержкой Redis, PostgreSQL или другого, то можно воспользоваться <a href="https://github.com/encode/broadcaster" class="external-link" target="_blank">encode/broadcaster</a>.
///
## Дополнительная информация { #more-info }
Для более глубокого изучения темы воспользуйтесь документацией Starlette:
* <a href="https://www.starlette.dev/websockets/" class="external-link" target="_blank">The `WebSocket` class</a>.
* <a href="https://www.starlette.dev/endpoints/#websocketendpoint" class="external-link" target="_blank">Class-based WebSocket handling</a>.
+35
View File
@@ -0,0 +1,35 @@
# Подключение WSGI — Flask, Django и другие { #including-wsgi-flask-django-others }
Вы можете монтировать WSGI‑приложения, как вы видели в [Подприложения — Mounts](sub-applications.md){.internal-link target=_blank}, [За прокси‑сервером](behind-a-proxy.md){.internal-link target=_blank}.
Для этого вы можете использовать `WSGIMiddleware` и обернуть им ваше WSGI‑приложение, например Flask, Django и т.д.
## Использование `WSGIMiddleware` { #using-wsgimiddleware }
Нужно импортировать `WSGIMiddleware`.
Затем оберните WSGI‑приложение (например, Flask) в middleware (Промежуточный слой).
После этого смонтируйте его на путь.
{* ../../docs_src/wsgi/tutorial001.py hl[2:3,3] *}
## Проверьте { #check-it }
Теперь каждый HTTP‑запрос по пути `/v1/` будет обрабатываться приложением Flask.
А всё остальное будет обрабатываться **FastAPI**.
Если вы запустите это и перейдёте по <a href="http://localhost:8000/v1/" class="external-link" target="_blank">http://localhost:8000/v1/</a>, вы увидите HTTP‑ответ от Flask:
```txt
Hello, World from Flask!
```
А если вы перейдёте по <a href="http://localhost:8000/v2" class="external-link" target="_blank">http://localhost:8000/v2</a>, вы увидите HTTP‑ответ от FastAPI:
```JSON
{
"message": "Hello World"
}
```
+485
View File
@@ -0,0 +1,485 @@
# Альтернативы, источники вдохновения и сравнения { #alternatives-inspiration-and-comparisons }
Что вдохновило **FastAPI**, сравнение с альтернативами и чему он у них научился.
## Введение { #intro }
**FastAPI** не существовал бы без предыдущих работ других людей.
Было создано множество инструментов, которые вдохновили на его появление.
Я несколько лет избегал создания нового фреймворка. Сначала пытался закрыть все возможности, которые сейчас предоставляет **FastAPI**, с помощью множества разных фреймворков, плагинов и инструментов.
Но в какой-то момент не осталось другого варианта, кроме как создать что-то, что включает все эти возможности, взяв лучшие идеи из прежних инструментов и совместив их максимально удачным образом, используя возможности языка, которых прежде не было (аннотации типов в Python 3.6+).
## Предшествующие инструменты { #previous-tools }
### <a href="https://www.djangoproject.com/" class="external-link" target="_blank">Django</a> { #django }
Это самый популярный Python-фреймворк, ему широко доверяют. Он используется для построения систем вроде Instagram.
Он относительно тесно связан с реляционными базами данных (например, MySQL или PostgreSQL), поэтому использовать NoSQL-базу данных (например, Couchbase, MongoDB, Cassandra и т. п.) в качестве основного хранилища не очень просто.
Он был создан для генерации HTML на бэкенде, а не для создания API, используемых современным фронтендом (например, React, Vue.js и Angular) или другими системами (например, устройствами <abbr title="Internet of Things Интернет вещей">IoT</abbr>), которые с ним общаются.
### <a href="https://www.django-rest-framework.org/" class="external-link" target="_blank">Django REST Framework</a> { #django-rest-framework }
Django REST Framework был создан как гибкий набор инструментов для построения веб-API поверх Django, чтобы улучшить его возможности в части API.
Он используется многими компаниями, включая Mozilla, Red Hat и Eventbrite.
Это был один из первых примеров **автоматической документации API**, и именно эта идея одной из первых вдохновила на «поиск» **FastAPI**.
/// note | Заметка
Django REST Framework был создан Томом Кристи. Он же создал Starlette и Uvicorn, на которых основан **FastAPI**.
///
/// check | Вдохновило **FastAPI** на
Наличие пользовательского веб-интерфейса с автоматической документацией API.
///
### <a href="https://flask.palletsprojects.com" class="external-link" target="_blank">Flask</a> { #flask }
Flask — это «микрофреймворк», он не включает интеграции с базами данных и многие другие вещи, которые в Django идут «из коробки».
Эта простота и гибкость позволяет, например, использовать NoSQL-базы в качестве основной системы хранения данных.
Он очень прост, его относительно легко интуитивно освоить, хотя местами документация довольно техническая.
Его также часто используют для приложений, которым не нужна база данных, управление пользователями или многие другие функции, предварительно встроенные в Django. Хотя многие из этих возможностей можно добавить плагинами.
Такое разбиение на части и то, что это «микрофреймворк», который можно расширять ровно под нужды, — ключевая особенность, которую хотелось сохранить.
С учётом простоты Flask он казался хорошим вариантом для создания API. Следующим было найти «Django REST Framework» для Flask.
/// check | Вдохновило **FastAPI** на
Быть микро-фреймворком. Облегчить комбинирование необходимых инструментов и компонентов.
Иметь простую и удобную систему маршрутизации.
///
### <a href="https://requests.readthedocs.io" class="external-link" target="_blank">Requests</a> { #requests }
**FastAPI** на самом деле не альтернатива **Requests**. Их области применения очень различны.
Обычно Requests используют даже внутри приложения FastAPI.
И всё же **FastAPI** во многом вдохновлялся Requests.
**Requests** — это библиотека для взаимодействия с API (как клиент), а **FastAPI** — библиотека для создания API (как сервер).
Они, в каком-то смысле, находятся на противоположных концах и дополняют друг друга.
Requests имеет очень простой и понятный дизайн, им очень легко пользоваться, есть разумные значения по умолчанию. И при этом он очень мощный и настраиваемый.
Именно поэтому на официальном сайте сказано:
> Requests — один из самых загружаемых Python-пакетов всех времён
Пользоваться им очень просто. Например, чтобы сделать запрос `GET`, вы бы написали:
```Python
response = requests.get("http://example.com/some/url")
```
Соответствующая в FastAPI API-операция пути могла бы выглядеть так:
```Python hl_lines="1"
@app.get("/some/url")
def read_url():
return {"message": "Hello World"}
```
Посмотрите, насколько похожи `requests.get(...)` и `@app.get(...)`.
/// check | Вдохновило **FastAPI** на
* Иметь простой и понятный API.
* Использовать названия HTTP-методов (операций) напрямую, простым и интуитивным образом.
* Иметь разумные значения по умолчанию, но и мощные настройки.
///
### <a href="https://swagger.io/" class="external-link" target="_blank">Swagger</a> / <a href="https://github.com/OAI/OpenAPI-Specification/" class="external-link" target="_blank">OpenAPI</a> { #swagger-openapi }
Главной возможностью, которую хотелось взять из Django REST Framework, была автоматическая документация API.
Затем я обнаружил, что есть стандарт для документирования API с использованием JSON (или YAML — расширения JSON), под названием Swagger.
И уже существовал веб-интерфейс для Swagger API. Поэтому возможность генерировать документацию Swagger для API позволила бы автоматически использовать этот веб-интерфейс.
В какой-то момент Swagger был передан Linux Foundation и переименован в OpenAPI.
Вот почему, говоря о версии 2.0, обычно говорят «Swagger», а о версии 3+ — «OpenAPI».
/// check | Вдохновило **FastAPI** на
Использовать открытый стандарт для спецификаций API вместо самодельной схемы.
И интегрировать основанные на стандартах инструменты пользовательского интерфейса:
* <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>
* <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>
Эти два инструмента выбраны за популярность и стабильность, но даже при беглом поиске можно найти десятки альтернативных интерфейсов для OpenAPI (которые можно использовать с **FastAPI**).
///
### REST-фреймворки для Flask { #flask-rest-frameworks }
Существует несколько REST-фреймворков для Flask, но, вложив время и усилия в исследование, я обнаружил, что многие из них прекращены или заброшены, с несколькими нерешёнными Issue (тикет\обращение), из-за которых они непригодны.
### <a href="https://marshmallow.readthedocs.io/en/stable/" class="external-link" target="_blank">Marshmallow</a> { #marshmallow }
Одна из основных возможностей, нужных системам API, — «<abbr title="также называемая маршаллингом или преобразованием">сериализация</abbr>» данных, то есть преобразование данных из кода (Python) во что-то, что можно отправить по сети. Например, преобразование объекта с данными из базы в JSON-объект. Преобразование объектов `datetime` в строки и т. п.
Ещё одна важная возможность, востребованная API, — валидация данных: убеждаться, что данные валидны с учётом заданных параметров. Например, что какое-то поле — `int`, а не произвольная строка. Это особенно полезно для входящих данных.
Без системы валидации данных вам пришлось бы выполнять все проверки вручную в коде.
Именно для этих возможностей и был создан Marshmallow. Это отличная библиотека, я много ей пользовался раньше.
Но она появилась до того, как в Python появились аннотации типов. Поэтому для определения каждой <abbr title="описание того, как данные должны быть сформированы">схемы</abbr> нужно использовать специальные утилиты и классы, предоставляемые Marshmallow.
/// check | Вдохновило **FastAPI** на
Использовать код для автоматического определения «схем», задающих типы данных и их валидацию.
///
### <a href="https://webargs.readthedocs.io/en/latest/" class="external-link" target="_blank">Webargs</a> { #webargs }
Ещё одна важная возможность для API — <abbr title="чтение и преобразование данных в объекты Python">парсинг</abbr> данных из входящих HTTP-запросов.
Webargs — это инструмент, созданный для этого поверх нескольких фреймворков, включая Flask.
Он использует Marshmallow для валидации данных. И создан теми же разработчиками.
Это отличный инструмент, и я тоже много им пользовался до появления **FastAPI**.
/// info | Информация
Webargs был создан теми же разработчиками, что и Marshmallow.
///
/// check | Вдохновило **FastAPI** на
Автоматическую валидацию входящих данных HTTP-запроса.
///
### <a href="https://apispec.readthedocs.io/en/stable/" class="external-link" target="_blank">APISpec</a> { #apispec }
Marshmallow и Webargs предоставляют валидацию, парсинг и сериализацию как плагины.
Но документации всё ещё не было. Тогда появился APISpec.
Это плагин для многих фреймворков (есть плагин и для Starlette).
Он работает так: вы пишете определение схемы в формате YAML внутри докстринга каждой функции, обрабатывающей маршрут.
И он генерирует схемы OpenAPI.
Так это работает во Flask, Starlette, Responder и т. д.
Но у нас снова возникает проблема: появляется микро-синтаксис внутри строки Python (большой YAML).
Редактор кода мало чем может помочь. И если мы изменим параметры или схемы Marshmallow и забудем также изменить YAML в докстринге, сгенерированная схема устареет.
/// info | Информация
APISpec был создан теми же разработчиками, что и Marshmallow.
///
/// check | Вдохновило **FastAPI** на
Поддержку открытого стандарта для API — OpenAPI.
///
### <a href="https://flask-apispec.readthedocs.io/en/latest/" class="external-link" target="_blank">Flask-apispec</a> { #flask-apispec }
Это плагин для Flask, который связывает Webargs, Marshmallow и APISpec.
Он использует информацию из Webargs и Marshmallow, чтобы автоматически генерировать схемы OpenAPI с помощью APISpec.
Отличный и недооценённый инструмент. Он заслуживает большей популярности, чем многие плагины для Flask. Возможно, из-за слишком краткой и абстрактной документации.
Это решило проблему необходимости писать YAML (ещё один синтаксис) в докстрингах Python.
Комбинация Flask, Flask-apispec с Marshmallow и Webargs была моим любимым бэкенд-стеком до создания **FastAPI**.
Его использование привело к созданию нескольких full-stack генераторов на Flask. Это основные стеки, которые я (и несколько внешних команд) использовали до сих пор:
* <a href="https://github.com/tiangolo/full-stack" class="external-link" target="_blank">https://github.com/tiangolo/full-stack</a>
* <a href="https://github.com/tiangolo/full-stack-flask-couchbase" class="external-link" target="_blank">https://github.com/tiangolo/full-stack-flask-couchbase</a>
* <a href="https://github.com/tiangolo/full-stack-flask-couchdb" class="external-link" target="_blank">https://github.com/tiangolo/full-stack-flask-couchdb</a>
И эти же full-stack генераторы стали основой для [Генераторов проектов **FastAPI**](project-generation.md){.internal-link target=_blank}.
/// info | Информация
Flask-apispec был создан теми же разработчиками, что и Marshmallow.
///
/// check | Вдохновило **FastAPI** на
Автоматическую генерацию схемы OpenAPI из того же кода, который определяет сериализацию и валидацию.
///
### <a href="https://nestjs.com/" class="external-link" target="_blank">NestJS</a> (и <a href="https://angular.io/" class="external-link" target="_blank">Angular</a>) { #nestjs-and-angular }
Это даже не Python. NestJS — это JavaScript/TypeScript-фреймворк на NodeJS, вдохновлённый Angular.
Он достигает чего-то отчасти похожего на то, что можно сделать с Flask-apispec.
В нём встроена система внедрения зависимостей, вдохновлённая Angular 2. Требуется предварительная регистрация «инжектируемых» компонентов (как и во всех известных мне системах внедрения зависимостей), что добавляет многословности и повторяемости кода.
Поскольку параметры описываются с помощью типов TypeScript (аналог аннотаций типов в Python), поддержка редактора весьма хороша.
Но так как данные о типах TypeScript не сохраняются после компиляции в JavaScript, он не может полагаться на типы для одновременного определения валидации, сериализации и документации. Из‑за этого и некоторых проектных решений для получения валидации, сериализации и автоматической генерации схем приходится добавлять декораторы во многих местах. В итоге это становится довольно многословным.
Он плохо справляется с вложенными моделями. Если JSON-тело запроса — это объект JSON, содержащий внутренние поля, которые сами являются вложенными объектами JSON, это нельзя как следует задокументировать и провалидировать.
/// check | Вдохновило **FastAPI** на
Использовать типы Python для отличной поддержки в редакторе кода.
Иметь мощную систему внедрения зависимостей. Найти способ минимизировать повторение кода.
///
### <a href="https://sanic.readthedocs.io/en/latest/" class="external-link" target="_blank">Sanic</a> { #sanic }
Это был один из первых чрезвычайно быстрых Python-фреймворков на основе `asyncio`. Он был сделан очень похожим на Flask.
/// note | Технические детали
Он использовал <a href="https://github.com/MagicStack/uvloop" class="external-link" target="_blank">`uvloop`</a> вместо стандартного цикла `asyncio` в Python. Это и сделало его таким быстрым.
Он явно вдохновил Uvicorn и Starlette, которые сейчас быстрее Sanic в открытых бенчмарках.
///
/// check | Вдохновило **FastAPI** на
Поиск способа достичь сумасшедшей производительности.
Именно поэтому **FastAPI** основан на Starlette, так как это самый быстрый доступный фреймворк (по данным сторонних бенчмарков).
///
### <a href="https://falconframework.org/" class="external-link" target="_blank">Falcon</a> { #falcon }
Falcon — ещё один высокопроизводительный Python-фреймворк, он минималистичен и служит основой для других фреймворков, таких как Hug.
Он спроектирован так, что функции получают два параметра: «request» и «response». Затем вы «читаете» части из запроса и «пишете» части в ответ. Из‑за такого дизайна невозможно объявить параметры запроса и тело запроса стандартными аннотациями типов Python как параметры функции.
Поэтому валидация данных, сериализация и документация должны выполняться в коде вручную, не автоматически. Либо должны быть реализованы во фреймворке поверх Falcon, как в Hug. Та же особенность есть и в других фреймворках, вдохновлённых дизайном Falcon — с одним объектом запроса и одним объектом ответа в параметрах.
/// check | Вдохновило **FastAPI** на
Поиск способов получить отличную производительность.
Вместе с Hug (так как Hug основан на Falcon) вдохновило **FastAPI** объявлять параметр `response` в функциях.
Хотя в FastAPI это необязательно, и используется в основном для установки HTTP-заголовков, cookie и альтернативных статус-кодов.
///
### <a href="https://moltenframework.com/" class="external-link" target="_blank">Molten</a> { #molten }
Я обнаружил Molten на ранних этапах создания **FastAPI**. И у него были очень похожие идеи:
* Основан на аннотациях типов Python.
* Валидация и документация из этих типов.
* Система внедрения зависимостей.
Он не использует стороннюю библиотеку для валидации, сериализации и документации, такую как Pydantic, — у него своя. Поэтому такие определения типов данных будет сложнее переиспользовать.
Требуются более многословные конфигурации. И так как он основан на WSGI (вместо ASGI), он не предназначен для использования преимуществ высокой производительности инструментов вроде Uvicorn, Starlette и Sanic.
Система внедрения зависимостей требует предварительной регистрации зависимостей, а зависимости разрешаются по объявленным типам. Поэтому невозможно объявить более одного «компонента», предоставляющего определённый тип.
Маршруты объявляются в одном месте, используя функции, объявленные в других местах (вместо декораторов, которые можно разместить прямо над функцией, обрабатывающей эндпоинт). Это ближе к тому, как это делает Django, чем Flask (и Starlette). Это разделяет в коде вещи, которые довольно тесно связаны.
/// check | Вдохновило **FastAPI** на
Определять дополнительные проверки типов данных, используя значение «по умолчанию» атрибутов модели. Это улучшает поддержку в редакторе кода, и раньше этого не было в Pydantic.
Фактически это вдохновило на обновление частей Pydantic, чтобы поддерживать такой же стиль объявления валидации (вся эта функциональность теперь уже есть в Pydantic).
///
### <a href="https://github.com/hugapi/hug" class="external-link" target="_blank">Hug</a> { #hug }
Hug был одним из первых фреймворков, реализовавших объявление типов параметров API с использованием аннотаций типов Python. Это была отличная идея, которая вдохновила и другие инструменты.
Он использовал собственные типы в объявлениях вместо стандартных типов Python, но это всё равно был огромный шаг вперёд.
Он также был одним из первых фреймворков, генерировавших собственную схему, описывающую весь API в JSON.
Он не был основан на стандартах вроде OpenAPI и JSON Schema. Поэтому интегрировать его с другими инструментами, такими как Swagger UI, было бы непросто. Но, опять же, это была очень инновационная идея.
У него есть интересная и необычная особенность: с помощью одного и того же фреймворка можно создавать и API, и CLI.
Так как он основан на предыдущем стандарте для синхронных веб-фреймворков Python (WSGI), он не может работать с WebSocket и прочим, хотя также демонстрирует высокую производительность.
/// info | Информация
Hug был создан Тимоти Кросли, тем же автором <a href="https://github.com/timothycrosley/isort" class="external-link" target="_blank">`isort`</a>, отличного инструмента для автоматической сортировки импортов в файлах Python.
///
/// check | Идеи, вдохновившие **FastAPI**
Hug вдохновил части APIStar и был одним из наиболее многообещающих инструментов, которые я нашёл, наряду с APIStar.
Hug помог вдохновить **FastAPI** использовать аннотации типов Python для объявления параметров и автоматически генерировать схему, определяющую API.
Hug вдохновил **FastAPI** объявлять параметр `response` в функциях для установки HTTP-заголовков и cookie.
///
### <a href="https://github.com/encode/apistar" class="external-link" target="_blank">APIStar</a> (<= 0.5) { #apistar-0-5 }
Прямо перед решением строить **FastAPI** я нашёл сервер **APIStar**. В нём было почти всё, что я искал, и отличный дизайн.
Это была одна из первых реализаций фреймворка, использующего аннотации типов Python для объявления параметров и запросов (до NestJS и Molten), которые я видел. Я обнаружил его примерно в то же время, что и Hug. Но APIStar использовал стандарт OpenAPI.
В нём были автоматические валидация данных, сериализация данных и генерация схемы OpenAPI на основе тех же аннотаций типов в нескольких местах.
Определение схемы тела запроса не использовало те же аннотации типов Python, как в Pydantic, — это было ближе к Marshmallow, поэтому поддержка редактора была бы хуже, но всё равно APIStar оставался лучшим доступным вариантом.
На тот момент у него были лучшие показатели в бенчмарках (его превосходил только Starlette).
Сначала у него не было веб‑UI для автоматической документации API, но я знал, что могу добавить к нему Swagger UI.
У него была система внедрения зависимостей. Она требовала предварительной регистрации компонентов, как и другие инструменты, обсуждавшиеся выше. Но всё же это была отличная возможность.
Мне так и не удалось использовать его в полном проекте, поскольку не было интеграции с системой безопасности, поэтому я не мог заменить все возможности, которые имел с full-stack генераторами на основе Flask-apispec. В моём бэклоге было создать пулл-реквест (запрос на изменение), добавляющий эту функциональность.
Затем фокус проекта сместился.
Это перестал быть веб-фреймворк для API, так как автору нужно было сосредоточиться на Starlette.
Сейчас APIStar — это набор инструментов для валидации спецификаций OpenAPI, а не веб-фреймворк.
/// info | Информация
APIStar был создан Томом Кристи. Тем самым человеком, который создал:
* Django REST Framework
* Starlette (на котором основан **FastAPI**)
* Uvicorn (используется Starlette и **FastAPI**)
///
/// check | Вдохновило **FastAPI** на
Существование.
Идея объявлять сразу несколько вещей (валидацию данных, сериализацию и документацию) с помощью одних и тех же типов Python, которые одновременно обеспечивают отличную поддержку в редакторе кода, показалась мне блестящей.
После долгих поисков похожего фреймворка и тестирования множества альтернатив APIStar был лучшим доступным вариантом.
Затем APIStar перестал существовать как сервер, а был создан Starlette — новая и лучшая основа для такой системы. Это стало окончательным вдохновением для создания **FastAPI**.
Я считаю **FastAPI** «духовным преемником» APIStar, который улучшает и расширяет возможности, систему типов и другие части, опираясь на уроки от всех этих предыдущих инструментов.
///
## Что используется в **FastAPI** { #used-by-fastapi }
### <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a> { #pydantic }
Pydantic — это библиотека для определения валидации данных, сериализации и документации (с использованием JSON Schema) на основе аннотаций типов Python.
Благодаря этому он чрезвычайно интуитивен.
Его можно сравнить с Marshmallow. Хотя в бенчмарках он быстрее Marshmallow. И поскольку он основан на тех же аннотациях типов Python, поддержка в редакторе кода отличная.
/// check | **FastAPI** использует его для
Обработки всей валидации данных, сериализации данных и автоматической документации моделей (на основе JSON Schema).
Затем **FastAPI** берёт эти данные JSON Schema и помещает их в OpenAPI, помимо всех прочих функций.
///
### <a href="https://www.starlette.dev/" class="external-link" target="_blank">Starlette</a> { #starlette }
Starlette — это лёгкий <abbr title="Новый стандарт построения асинхронных веб-сервисов Python">ASGI</abbr> фреймворк/набор инструментов, идеально подходящий для создания высокопроизводительных asyncio‑сервисов.
Он очень простой и интуитивный. Спроектирован так, чтобы его было легко расширять, и чтобы компоненты были модульными.
В нём есть:
* Впечатляющая производительность.
* Поддержка WebSocket.
* Фоновые задачи, выполняемые в том же процессе.
* События запуска и завершения.
* Тестовый клиент на базе HTTPX.
* CORS, GZip, статические файлы, потоковые ответы.
* Поддержка сессий и cookie.
* 100% покрытие тестами.
* 100% кодовой базы с аннотациями типов.
* Мало жёстких зависимостей.
В настоящее время Starlette — самый быстрый из протестированных Python-фреймворков. Его превосходит только Uvicorn, который не фреймворк, а сервер.
Starlette предоставляет весь базовый функционал веб-микрофреймворка.
Но он не предоставляет автоматическую валидацию данных, сериализацию или документацию.
Это одна из главных вещей, которые **FastAPI** добавляет поверх, всё на основе аннотаций типов Python (с использованием Pydantic). Плюс система внедрения зависимостей, утилиты безопасности, генерация схемы OpenAPI и т. д.
/// note | Технические детали
ASGI — это новый «стандарт», разрабатываемый участниками core-команды Django. Он всё ещё не является «стандартом Python» (PEP), хотя процесс идёт.
Тем не менее его уже используют как «стандарт» несколько инструментов. Это сильно улучшает совместимость: вы можете заменить Uvicorn на любой другой ASGI-сервер (например, Daphne или Hypercorn) или добавить совместимые с ASGI инструменты, такие как `python-socketio`.
///
/// check | **FastAPI** использует его для
Обработки всех основных веб-частей. Добавляя возможности поверх.
Класс `FastAPI` напрямую наследуется от класса `Starlette`.
Так что всё, что вы можете сделать со Starlette, вы можете сделать напрямую с **FastAPI**, по сути это «Starlette на стероидах».
///
### <a href="https://www.uvicorn.dev/" class="external-link" target="_blank">Uvicorn</a> { #uvicorn }
Uvicorn — молниеносный ASGI-сервер, построенный на uvloop и httptools.
Это не веб-фреймворк, а сервер. Например, он не предоставляет инструменты для маршрутизации по путям. Это предоставляет сверху фреймворк, такой как Starlette (или **FastAPI**).
Это рекомендуемый сервер для Starlette и **FastAPI**.
/// check | **FastAPI** рекомендует его как
Основной веб-сервер для запуска приложений **FastAPI**.
Также вы можете использовать опцию командной строки `--workers`, чтобы получить асинхронный многопроцессный сервер.
Подробнее см. раздел [Развёртывание](deployment/index.md){.internal-link target=_blank}.
///
## Бенчмарки и скорость { #benchmarks-and-speed }
Чтобы понять, сравнить и увидеть разницу между Uvicorn, Starlette и FastAPI, см. раздел о [Бенчмарках](benchmarks.md){.internal-link target=_blank}.
+444
View File
@@ -0,0 +1,444 @@
# Конкурентность и async / await { #concurrency-and-async-await }
Подробности о синтаксисе `async def` для *функций-обработчиков пути* и немного фона об асинхронном коде, конкурентности и параллелизме.
## Нет времени? { #in-a-hurry }
<abbr title="too long; didn't read слишком длинно; не читал"><strong>TL;DR:</strong></abbr>
Если вы используете сторонние библиотеки, которые нужно вызывать с `await`, например:
```Python
results = await some_library()
```
Тогда объявляйте *функции-обработчики пути* с `async def`, например:
```Python hl_lines="2"
@app.get('/')
async def read_results():
results = await some_library()
return results
```
/// note | Примечание
`await` можно использовать только внутри функций, объявленных с `async def`.
///
---
Если вы используете стороннюю библиотеку, которая взаимодействует с чем-то (база данных, API, файловая система и т.д.) и не поддерживает использование `await` (сейчас это относится к большинству библиотек для БД), тогда объявляйте *функции-обработчики пути* как обычно, просто с `def`, например:
```Python hl_lines="2"
@app.get('/')
def results():
results = some_library()
return results
```
---
Если вашему приложению (по какой-то причине) не нужно ни с чем взаимодействовать и ждать ответа, используйте `async def`, даже если внутри не нужен `await`.
---
Если вы просто не уверены, используйте обычный `def`.
---
**Примечание**: вы можете смешивать `def` и `async def` в *функциях-обработчиках пути* столько, сколько нужно, и объявлять каждую так, как лучше для вашего случая. FastAPI сделает с ними всё как надо.
В любом из случаев выше FastAPI всё равно работает асинхронно и очень быстро.
Но следуя этим шагам, он сможет выполнить некоторые оптимизации производительности.
## Технические подробности { #technical-details }
Современные версии Python поддерживают **«асинхронный код»** с помощью **«сопрограмм»** (coroutines) и синтаксиса **`async` и `await`**.
Разберём эту фразу по частям в разделах ниже:
* **Асинхронный код**
* **`async` и `await`**
* **Сопрограммы**
## Асинхронный код { #asynchronous-code }
Асинхронный код значит, что в языке 💬 есть способ сказать компьютеру/программе 🤖, что в некоторый момент кода ему 🤖 придётся подождать, пока *что-то ещё* где-то в другом месте завершится. Назовём это *что-то ещё* «медленный файл» 📝.
И пока мы ждём завершения работы с «медленныи файлом» 📝, компьютер может заняться другой работой.
Затем компьютер/программа 🤖 будет возвращаться каждый раз, когда появится возможность (пока снова где-то идёт ожидание), или когда 🤖 завершит всю текущую работу. И он 🤖 проверит, не завершилась ли какая-либо из задач, которых он ждал, и сделает то, что нужно.
Далее он 🤖 возьмёт первую завершившуюся задачу (скажем, наш «медленный файл» 📝) и продолжит делать с ней то, что требуется.
Это «ожидание чего-то ещё» обычно относится к операциям <abbr title="Input and Output Ввод/вывод">I/O</abbr>, которые относительно «медленные» (по сравнению со скоростью процессора и оперативной памяти), например ожидание:
* отправки данных клиентом по сети
* получения клиентом данных, отправленных вашей программой по сети
* чтения системой содержимого файла на диске и передачи этих данных вашей программе
* записи на диск содержимого, которое ваша программа передала системе
* операции удалённого API
* завершения операции базы данных
* возврата результатов запроса к базе данных
* и т.д.
Поскольку основное время выполнения уходит на ожидание операций <abbr title="Input and Output Ввод/вывод">I/O</abbr>, их называют операциями, «ограниченными вводом-выводом» (I/O bound).
Это называется «асинхронным», потому что компьютеру/программе не нужно «синхронизироваться» с медленной задачей, простаивая и выжидая точный момент её завершения, чтобы забрать результат и продолжить работу.
Вместо этого, в «асинхронной» системе, уже завершившаяся задача может немного подождать (несколько микросекунд) в очереди, пока компьютер/программа завершит то, чем занимался, и затем вернётся, чтобы забрать результаты и продолжить работу с ними.
Для «синхронного» (в противоположность «асинхронному») исполнения часто используют термин «последовательный», потому что компьютер/программа выполняет все шаги по порядку, прежде чем переключиться на другую задачу, даже если эти шаги включают ожидание.
### Конкурентность и бургеры { #concurrency-and-burgers }
Та идея **асинхронного** кода, описанная выше, иногда также называется **«конкурентностью»**. Она отличается от **«параллелизма»**.
И **конкурентность**, и **параллелизм** относятся к «разным вещам, происходящим примерно одновременно».
Но различия между *конкурентностью* и *параллелизмом* довольно существенные.
Чтобы их увидеть, представьте следующую историю про бургеры:
### Конкурентные бургеры { #concurrent-burgers }
Вы идёте со своей возлюбленной за фастфудом, вы стоите в очереди, пока кассир принимает заказы у людей перед вами. 😍
<img src="/img/async/concurrent-burgers/concurrent-burgers-01.png" class="illustration">
Наконец ваша очередь: вы заказываете 2 очень «навороченных» бургера — для вашей возлюбленной и для себя. 🍔🍔
<img src="/img/async/concurrent-burgers/concurrent-burgers-02.png" class="illustration">
Кассир говорит что-то повару на кухне, чтобы они знали, что нужно приготовить ваши бургеры (хотя сейчас они готовят бургеры для предыдущих клиентов).
<img src="/img/async/concurrent-burgers/concurrent-burgers-03.png" class="illustration">
Вы платите. 💸
Кассир выдаёт вам номер вашей очереди.
<img src="/img/async/concurrent-burgers/concurrent-burgers-04.png" class="illustration">
Пока вы ждёте, вы вместе со своей возлюбленной идёте и выбираете столик, садитесь и долго болтаете (ваши бургеры очень «навороченные», поэтому им нужно время на приготовление).
Сидя за столиком со своей возлюбленной в ожидании бургеров, вы можете провести это время, восхищаясь тем, какая она классная, милая и умная ✨😍✨.
<img src="/img/async/concurrent-burgers/concurrent-burgers-05.png" class="illustration">
Пока вы ждёте и разговариваете, время от времени вы поглядываете на номер на табло, чтобы понять, не подошла ли уже ваша очередь.
И вот в какой-то момент ваша очередь наступает. Вы подходите к стойке, забираете свои бургеры и возвращаетесь к столику.
<img src="/img/async/concurrent-burgers/concurrent-burgers-06.png" class="illustration">
Вы со своей возлюбленной едите бургеры и отлично проводите время. ✨
<img src="/img/async/concurrent-burgers/concurrent-burgers-07.png" class="illustration">
/// info | Информация
Прекрасные иллюстрации от <a href="https://www.instagram.com/ketrinadrawsalot" class="external-link" target="_blank">Ketrina Thompson</a>. 🎨
///
---
Представьте, что в этой истории вы — компьютер/программа 🤖.
Пока вы стоите в очереди, вы просто бездействуете 😴, ждёте своей очереди и не делаете ничего особо «продуктивного». Но очередь движется быстро, потому что кассир только принимает заказы (а не готовит их), так что это нормально.
Когда приходит ваша очередь, вы выполняете действительно «продуктивную» работу: просматриваете меню, решаете, чего хотите, учитываете выбор своей возлюбленной, платите, проверяете, что дали правильную купюру/карту, что сумма списана корректно, что в заказе верные позиции и т.д.
Но затем, хотя у вас ещё нет бургеров, ваша «работа» с кассиром поставлена «на паузу» ⏸, потому что нужно подождать 🕙, пока бургеры будут готовы.
Но, отойдя от стойки и сев за столик с номерком, вы можете переключить 🔀 внимание на свою возлюбленную и «поработать» ⏯ 🤓 над этим. Снова очень «продуктивно» — флирт с вашей возлюбленной 😍.
Потом кассир 💁 «говорит»: «Я закончил делать бургеры», — выводя ваш номер на табло, но вы не подпрыгиваете как сумасшедший в ту же секунду, как только номер сменился на ваш. Вы знаете, что ваши бургеры никто не украдёт, потому что у вас есть номер вашей очереди, а у других — их.
Поэтому вы дожидаетесь, пока ваша возлюбленная закончит историю (завершится текущая работа ⏯ / выполняемая задача 🤓), мягко улыбаетесь и говорите, что идёте за бургерами ⏸.
Затем вы идёте к стойке 🔀, к исходной задаче, которая теперь завершена ⏯, забираете бургеры, благодарите и несёте их к столику. На этом шаг/задача взаимодействия со стойкой завершён ⏹. Это, в свою очередь, создаёт новую задачу — «есть бургеры» 🔀 ⏯, но предыдущая «получить бургеры» — завершена ⏹.
### Параллельные бургеры { #parallel-burgers }
Теперь представим, что это не «Конкурентные бургеры», а «Параллельные бургеры».
Вы идёте со своей возлюбленной за параллельным фастфудом.
Вы стоите в очереди, пока несколько (скажем, 8) кассиров, которые одновременно являются поварами, принимают заказы у людей перед вами.
Все перед вами ждут, пока их бургеры будут готовы, не отходя от стойки, потому что каждый из 8 кассиров сразу идёт готовить бургер перед тем, как принять следующий заказ.
<img src="/img/async/parallel-burgers/parallel-burgers-01.png" class="illustration">
Наконец ваша очередь: вы заказываете 2 очень «навороченных» бургера — для вашей возлюбленной и для себя.
Вы платите 💸.
<img src="/img/async/parallel-burgers/parallel-burgers-02.png" class="illustration">
Кассир уходит на кухню.
Вы ждёте, стоя у стойки 🕙, чтобы никто не забрал ваши бургеры раньше вас, так как никаких номерков нет.
<img src="/img/async/parallel-burgers/parallel-burgers-03.png" class="illustration">
Так как вы со своей возлюбленной заняты тем, чтобы никто не встал перед вами и не забрал ваши бургеры, как только они появятся, вы не можете уделить внимание своей возлюбленной. 😞
Это «синхронная» работа, вы «синхронизированы» с кассиром/поваром 👨‍🍳. Вам нужно ждать 🕙 и находиться там в точный момент, когда кассир/повар 👨‍🍳 закончит бургеры и вручит их вам, иначе их может забрать кто-то другой.
<img src="/img/async/parallel-burgers/parallel-burgers-04.png" class="illustration">
Затем ваш кассир/повар 👨‍🍳 наконец возвращается с вашими бургерами, после долгого ожидания 🕙 у стойки.
<img src="/img/async/parallel-burgers/parallel-burgers-05.png" class="illustration">
Вы берёте бургеры и идёте со своей возлюбленной к столику.
Вы просто их съедаете — и всё. ⏹
<img src="/img/async/parallel-burgers/parallel-burgers-06.png" class="illustration">
Разговоров и флирта было немного, потому что большую часть времени вы ждали 🕙 у стойки. 😞
/// info | Информация
Прекрасные иллюстрации от <a href="https://www.instagram.com/ketrinadrawsalot" class="external-link" target="_blank">Ketrina Thompson</a>. 🎨
///
---
В этом сценарии «параллельных бургеров» вы — компьютер/программа 🤖 с двумя процессорами (вы и ваша возлюбленная), оба ждут 🕙 и уделяют внимание ⏯ тому, чтобы «ждать у стойки» 🕙 долгое время.
В ресторане 8 процессоров (кассиров/поваров). Тогда как в «конкурентных бургерах» могло быть только 2 (один кассир и один повар).
И всё же финальный опыт — не самый лучший. 😞
---
Это была параллельная версия истории про бургеры. 🍔
Для более «жизненного» примера представьте банк.
До недавнего времени в большинстве банков было несколько кассиров 👨‍💼👨‍💼👨‍💼👨‍💼 и длинная очередь 🕙🕙🕙🕙🕙🕙🕙🕙.
Все кассиры делают всю работу с одним клиентом за другим 👨‍💼⏯.
И вам приходится долго 🕙 стоять в очереди, иначе вы потеряете свою очередь.
Вы вряд ли захотите взять свою возлюбленную 😍 с собой, чтобы заняться делами в банке 🏦.
### Вывод про бургеры { #burger-conclusion }
В этом сценарии «фастфуда с вашей возлюбленной», так как много ожидания 🕙, гораздо логичнее иметь конкурентную систему ⏸🔀⏯.
Так обстоит дело и с большинством веб-приложений.
Очень много пользователей, но ваш сервер ждёт 🕙, пока их не самое хорошее соединение отправит их запросы.
А затем снова ждёт 🕙, пока отправятся ответы.
Это «ожидание» 🕙 измеряется микросекундами, но если всё сложить, то в сумме получается много ожидания.
Вот почему асинхронный ⏸🔀⏯ код очень уместен для веб-API.
Именно такая асинхронность сделала NodeJS популярным (хотя NodeJS — не параллельный), и это сильная сторона Go как языка программирования.
Того же уровня производительности вы получаете с **FastAPI**.
А так как можно одновременно использовать параллелизм и асинхронность, вы получаете производительность выше, чем у большинства протестированных фреймворков на NodeJS и на уровне Go, который — компилируемый язык, ближе к C <a href="https://www.techempower.com/benchmarks/#section=data-r17&hw=ph&test=query&l=zijmkf-1" class="external-link" target="_blank">(всё благодаря Starlette)</a>.
### Конкурентность лучше параллелизма? { #is-concurrency-better-than-parallelism }
Нет! Мораль истории не в этом.
Конкурентность отличается от параллелизма. И она лучше в **конкретных** сценариях, где много ожидания. Поэтому при разработке веб-приложений она обычно намного лучше параллелизма. Но не во всём.
Чтобы уравновесить это, представьте такую короткую историю:
> Вам нужно убрать большой грязный дом.
*Да, это вся история*.
---
Здесь нигде нет ожидания 🕙, просто очень много работы в разных местах дома.
Можно организовать «очереди» как в примере с бургерами — сначала гостиная, потом кухня, — но так как вы ничего не ждёте 🕙, а просто убираете и убираете, очереди ни на что не повлияют.
На завершение уйдёт одинаковое время — с очередями (конкурентностью) и без них — и объём выполненной работы будет одинаковым.
Но в этом случае, если бы вы могли привести 8 бывших кассиров/поваров, а теперь — уборщиков, и каждый из них (плюс вы) взял бы свою зону дома для уборки, вы могли бы сделать всю работу **параллельно**, с дополнительной помощью, и завершить гораздо быстрее.
В этом сценарии каждый уборщик (включая вас) был бы процессором, выполняющим свою часть работы.
И так как основное время выполнения уходит на реальную работу (а не ожидание), а работу в компьютере выполняет <abbr title="Central Processing Unit – Центральный процессор">CPU</abbr>, такие задачи называют «ограниченными процессором» (CPU bound).
---
Типичные примеры CPU-bound операций — те, которые требуют сложной математической обработки.
Например:
* Обработка **аудио** или **изображений**.
* **Компьютерное зрение**: изображение состоит из миллионов пикселей, каждый пиксель имеет 3 значения/цвета; обычно требуется вычислить что-то для всех этих пикселей одновременно.
* **Машинное обучение**: обычно требует множества умножений «матриц» и «векторов». Представьте огромную таблицу с числами и умножение всех этих чисел «одновременно».
* **Глубокое обучение**: это подполе Машинного обучения, так что всё вышесказанное применимо. Просто это не одна таблица чисел, а их огромный набор, и во многих случаях вы используете специальный процессор, чтобы строить и/или использовать такие модели.
### Конкурентность + параллелизм: Веб + Машинное обучение { #concurrency-parallelism-web-machine-learning }
С **FastAPI** вы можете использовать преимущества конкурентности, что очень распространено в веб-разработке (это та же основная «фишка» NodeJS).
Но вы также можете использовать выгоды параллелизма и многопроцессности (когда несколько процессов работают параллельно) для рабочих нагрузок, **ограниченных процессором** (CPU bound), как в системах Машинного обучения.
Плюс к этому простой факт, что Python — основной язык для **Data Science**, Машинного обучения и особенно Глубокого обучения, делает FastAPI очень хорошим выбором для веб-API и приложений в области Data Science / Машинного обучения (среди многих других).
Как добиться такого параллелизма в продакшн, см. раздел [Развёртывание](deployment/index.md){.internal-link target=_blank}.
## `async` и `await` { #async-and-await }
В современных версиях Python есть очень интуитивный способ определять асинхронный код. Это делает его похожим на обычный «последовательный» код, а «ожидание» выполняется за вас в нужные моменты.
Когда есть операция, которой нужно подождать перед тем, как вернуть результат, и она поддерживает эти новые возможности Python, вы можете написать так:
```Python
burgers = await get_burgers(2)
```
Ключ здесь — `await`. Он говорит Python, что нужно подождать ⏸, пока `get_burgers(2)` закончит своё дело 🕙, прежде чем сохранять результат в `burgers`. Благодаря этому Python будет знать, что за это время можно заняться чем-то ещё 🔀 ⏯ (например, принять другой запрос).
Чтобы `await` работал, он должен находиться внутри функции, которая поддерживает такую асинхронность. Для этого просто объявите её с `async def`:
```Python hl_lines="1"
async def get_burgers(number: int):
# Сделать что-то асинхронное, чтобы приготовить бургеры
return burgers
```
...вместо `def`:
```Python hl_lines="2"
# Это не асинхронный код
def get_sequential_burgers(number: int):
# Сделать что-то последовательное, чтобы приготовить бургеры
return burgers
```
С `async def` Python знает, что внутри этой функции нужно учитывать выражения `await` и что выполнение такой функции можно «приостанавливать» ⏸ и идти делать что-то ещё 🔀, чтобы потом вернуться.
Когда вы хотите вызвать функцию, объявленную с `async def`, нужно её «ожидать». Поэтому вот так не сработает:
```Python
# Это не сработает, потому что get_burgers определена с: async def
burgers = get_burgers(2)
```
---
Итак, если вы используете библиотеку, которую можно вызывать с `await`, вам нужно создать *функцию-обработчик пути*, которая её использует, с `async def`, например:
```Python hl_lines="2-3"
@app.get('/burgers')
async def read_burgers():
burgers = await get_burgers(2)
return burgers
```
### Более технические подробности { #more-technical-details }
Вы могли заметить, что `await` можно использовать только внутри функций, определённых с `async def`.
Но при этом функции, определённые с `async def`, нужно «ожидать». Значит, функции с `async def` тоже можно вызывать только из функций, определённых с `async def`.
Так что же с «яйцом и курицей» — как вызвать первую `async` функцию?
Если вы работаете с **FastAPI**, вам не о чем беспокоиться, потому что этой «первой» функцией будет ваша *функция-обработчик пути*, а FastAPI знает, как сделать всё правильно.
Но если вы хотите использовать `async` / `await` без FastAPI, вы тоже можете это сделать.
### Пишите свой асинхронный код { #write-your-own-async-code }
Starlette (и **FastAPI**) основаны на <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a>, что делает их совместимыми и со стандартной библиотекой Python <a href="https://docs.python.org/3/library/asyncio-task.html" class="external-link" target="_blank">asyncio</a>, и с <a href="https://trio.readthedocs.io/en/stable/" class="external-link" target="_blank">Trio</a>.
В частности, вы можете напрямую использовать <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a> для продвинутых сценариев конкурентности, где в вашем коде нужны более сложные паттерны.
И даже если вы не используете FastAPI, вы можете писать свои асинхронные приложения с <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a>, чтобы они были максимально совместимыми и получали его преимущества (например, *структурную конкурентность*).
Я создал ещё одну библиотеку поверх AnyIO, тонкий слой, чтобы немного улучшить аннотации типов и получить более качественное **автозавершение**, **ошибки прямо в редакторе** и т.д. Там также есть дружелюбное введение и руководство, чтобы помочь вам **понять** и писать **свой собственный асинхронный код**: <a href="https://asyncer.tiangolo.com/" class="external-link" target="_blank">Asyncer</a>. Она особенно полезна, если вам нужно **комбинировать асинхронный код с обычным** (блокирующим/синхронным) кодом.
### Другие формы асинхронного кода { #other-forms-of-asynchronous-code }
Такой стиль использования `async` и `await` относительно новый в языке.
Но он сильно упрощает работу с асинхронным кодом.
Такой же (или почти такой же) синтаксис недавно появился в современных версиях JavaScript (в браузере и NodeJS).
До этого работа с асинхронным кодом была заметно сложнее и труднее для понимания.
В предыдущих версиях Python можно было использовать потоки или <a href="https://www.gevent.org/" class="external-link" target="_blank">Gevent</a>. Но такой код гораздо сложнее понимать, отлаживать и держать в голове.
В прежних версиях NodeJS/браузерного JavaScript вы бы использовали «callbacks» (обратные вызовы), что приводит к «callback hell» (ад обратных вызовов).
## Сопрограммы { #coroutines }
**Сопрограмма** (coroutine) — это просто «навороченное» слово для того, что возвращает функция `async def`. Python знает, что это похоже на функцию: её можно запустить, она когда-нибудь завершится, но её выполнение может приостанавливаться ⏸ внутри, когда встречается `await`.
Часто всю функциональность использования асинхронного кода с `async` и `await` кратко называют «сопрограммами». Это сопоставимо с ключевой особенностью Go — «goroutines».
## Заключение { #conclusion }
Вернёмся к той же фразе:
> Современные версии Python поддерживают **«асинхронный код»** с помощью **«сопрограмм»** (coroutines) и синтаксиса **`async` и `await`**.
Теперь это должно звучать понятнее. ✨
Именно это «движет» FastAPI (через Starlette) и обеспечивает столь впечатляющую производительность.
## Очень технические подробности { #very-technical-details }
/// warning | Предупреждение
Скорее всего, этот раздел можно пропустить.
Здесь — очень технические подробности о том, как **FastAPI** работает «под капотом».
Если у вас есть достаточно технических знаний (сопрограммы, потоки, блокировки и т.д.) и вам интересно, как FastAPI обрабатывает `async def` по сравнению с обычным `def`, — вперёд.
///
### Функции-обработчики пути { #path-operation-functions }
Когда вы объявляете *функцию-обработчик пути* обычным `def` вместо `async def`, она запускается во внешнем пуле потоков, который затем «ожидается», вместо прямого вызова (прямой вызов заблокировал бы сервер).
Если вы пришли из другого async-фреймворка, который работает иначе, и привыкли объявлять тривиальные *функции-обработчики пути*, выполняющие только вычисления, через простой `def` ради крошечной выгоды в производительности (около 100 наносекунд), обратите внимание: в **FastAPI** эффект будет противоположным. В таких случаях лучше использовать `async def`, если только ваши *функции-обработчики пути* не используют код, выполняющий блокирующий <abbr title="Input/Output Ввод/вывод: чтение или запись на диск, сетевые соединения.">I/O</abbr>.
Тем не менее, в обоих случаях велика вероятность, что **FastAPI** [всё равно будет быстрее](index.md#performance){.internal-link target=_blank} (или как минимум сопоставим) с вашим предыдущим фреймворком.
### Зависимости { #dependencies }
То же относится к [зависимостям](tutorial/dependencies/index.md){.internal-link target=_blank}. Если зависимость — это обычная функция `def`, а не `async def`, она запускается во внешнем пуле потоков.
### Подзависимости { #sub-dependencies }
У вас может быть несколько зависимостей и [подзависимостей](tutorial/dependencies/sub-dependencies.md){.internal-link target=_blank}, которые требуют друг друга (в виде параметров определений функций): часть из них может быть объявлена с `async def`, а часть — обычным `def`. Всё будет работать, а те, что объявлены обычным `def`, будут вызываться во внешнем потоке (из пула), а не «ожидаться».
### Другие служебные функции { #other-utility-functions }
Любые другие служебные функции, которые вы вызываете напрямую, можно объявлять обычным `def` или `async def`, и FastAPI не будет влиять на то, как вы их вызываете.
В отличие от функций, которые FastAPI вызывает за вас: *функции-обработчики пути* и зависимости.
Если служебная функция — обычная функция с `def`, она будет вызвана напрямую (как вы и пишете в коде), не в пуле потоков; если функция объявлена с `async def`, тогда при её вызове в вашем коде вы должны использовать `await`.
---
Снова: это очень технические подробности, полезные, вероятно, только если вы целенаправленно их ищете.
Иначе вам достаточно руководствоваться рекомендациями из раздела выше: <a href="#in-a-hurry">Нет времени?</a>.
+34
View File
@@ -0,0 +1,34 @@
# Бенчмарки (тесты производительности) { #benchmarks }
Независимые бенчмарки TechEmpower показывают, что приложения **FastAPI** под управлением Uvicorn — <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" class="external-link" target="_blank">одни из самых быстрых Python‑фреймворков</a>, уступающие только Starlette и самому Uvicorn (используются внутри FastAPI).
Но при просмотре бенчмарков и сравнений следует иметь в виду следующее.
## Бенчмарки и скорость { #benchmarks-and-speed }
При проверке бенчмарков часто можно увидеть, что инструменты разных типов сравнивают как эквивалентные.
В частности, часто сравнивают вместе Uvicorn, Starlette и FastAPI (среди многих других инструментов).
Чем проще задача, которую решает инструмент, тем выше его производительность. И большинство бенчмарков не тестируют дополнительные возможности, предоставляемые инструментом.
Иерархия выглядит так:
* **Uvicorn**: ASGI-сервер
* **Starlette**: (использует Uvicorn) веб-микрофреймворк
* **FastAPI**: (использует Starlette) API-микрофреймворк с рядом дополнительных возможностей для создания API, включая валидацию данных и т. п.
* **Uvicorn**:
* Будет иметь наилучшую производительность, так как помимо самого сервера у него немного дополнительного кода.
* Вы не будете писать приложение непосредственно на Uvicorn. Это означало бы, что Ваш код должен включать как минимум весь код, предоставляемый Starlette (или **FastAPI**). И если Вы так сделаете, то в конечном итоге Ваше приложение будет иметь те же накладные расходы, что и при использовании фреймворка, минимизирующего код Вашего приложения и Ваши ошибки.
* Если Вы сравниваете Uvicorn, сравнивайте его с Daphne, Hypercorn, uWSGI и т. д. — серверами приложений.
* **Starlette**:
* Будет на следующем месте по производительности после Uvicorn. Фактически Starlette запускается под управлением Uvicorn, поэтому он может быть только «медленнее» Uvicorn из‑за выполнения большего объёма кода.
* Зато он предоставляет Вам инструменты для создания простых веб‑приложений с маршрутизацией по путям и т. п.
* Если Вы сравниваете Starlette, сравнивайте его с Sanic, Flask, Django и т. д. — веб‑фреймворками (или микрофреймворками).
* **FastAPI**:
* Точно так же, как Starlette использует Uvicorn и не может быть быстрее него, **FastAPI** использует Starlette, поэтому не может быть быстрее его.
* FastAPI предоставляет больше возможностей поверх Starlette — те, которые почти всегда нужны при создании API, такие как валидация и сериализация данных. В довесок Вы ещё и получаете автоматическую документацию (автоматическая документация даже не увеличивает накладные расходы при работе приложения, так как она создаётся при запуске).
* Если бы Вы не использовали FastAPI, а использовали Starlette напрямую (или другой инструмент вроде Sanic, Flask, Responder и т. д.), Вам пришлось бы самостоятельно реализовать валидацию и сериализацию данных. То есть, в итоге, Ваше приложение имело бы такие же накладные расходы, как если бы оно было создано с использованием FastAPI. И во многих случаях валидация и сериализация данных представляют собой самый большой объём кода, написанного в приложениях.
* Таким образом, используя FastAPI, Вы экономите время разработки, уменьшаете количество ошибок, строк кода и, вероятно, получите ту же производительность (или лучше), как и если бы не использовали его (поскольку Вам пришлось бы реализовать все его возможности в своём коде).
* Если Вы сравниваете FastAPI, сравнивайте его с фреймворком веб‑приложений (или набором инструментов), который обеспечивает валидацию данных, сериализацию и документацию, такими как Flask-apispec, NestJS, Molten и им подобные. Фреймворки с интегрированной автоматической валидацией данных, сериализацией и документацией.
+16
View File
@@ -0,0 +1,16 @@
# Развертывание FastAPI у облачных провайдеров { #deploy-fastapi-on-cloud-providers }
Вы можете использовать практически любого облачного провайдера, чтобы развернуть свое приложение на FastAPI.
В большинстве случаев у основных облачных провайдеров есть руководства по развертыванию FastAPI на их платформе.
## Облачные провайдеры — спонсоры { #cloud-providers-sponsors }
Некоторые облачные провайдеры ✨ [**спонсируют FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨ — это обеспечивает непрерывное и здоровое развитие FastAPI и его экосистемы.
И это показывает их искреннюю приверженность FastAPI и его сообществу (вам): они не только хотят предоставить вам хороший сервис, но и стремятся гарантировать, что у вас будет хороший и стабильный фреймворк — FastAPI. 🙇
Возможно, вы захотите попробовать их сервисы и воспользоваться их руководствами:
* <a href="https://docs.render.com/deploy-fastapi?utm_source=deploydoc&utm_medium=referral&utm_campaign=fastapi" class="external-link" target="_blank">Render</a>
* <a href="https://docs.railway.com/guides/fastapi?utm_medium=integration&utm_source=docs&utm_campaign=fastapi" class="external-link" target="_blank">Railway</a>
+321
View File
@@ -0,0 +1,321 @@
# Концепции развёртывания { #deployments-concepts }
При развёртывании приложения **FastAPI** (и вообще любого веб‑API) есть несколько концепций, о которых стоит думать — с их помощью можно выбрать **наиболее подходящий** способ **развёртывания вашего приложения**.
Некоторые из важных концепций:
* Безопасность — HTTPS
* Запуск при старте
* Перезапуски
* Репликация (количество запущенных процессов)
* Память
* Предварительные шаги перед запуском
Посмотрим, как они влияют на **развёртывания**.
В конечном итоге цель — **обслуживать клиентов вашего API** безопасно, **избегать перебоев** и максимально эффективно использовать **вычислительные ресурсы** (например, удалённые серверы/виртуальные машины). 🚀
Здесь я немного расскажу о этих **концепциях**, чтобы у вас появилась **интуиция**, как развёртывать ваш API в разных окружениях, возможно даже в **будущих**, которых ещё не существует.
Учитывая эти концепции, вы сможете **оценить и спроектировать** лучший способ развёртывания **своих API**.
В следующих главах я дам более **конкретные рецепты** по развёртыванию приложений FastAPI.
А пока давайте разберём важные **идеи**. Эти концепции применимы и к другим типам веб‑API. 💡
## Безопасность — HTTPS { #security-https }
В [предыдущей главе про HTTPS](https.md){.internal-link target=_blank} мы разобрались, как HTTPS обеспечивает шифрование для вашего API.
Также мы увидели, что HTTPS обычно обеспечивает компонент, **внешний** по отношению к серверу вашего приложения — **TLS Termination Proxy**.
И должен быть компонент, отвечающий за **обновление HTTPS‑сертификатов** — это может быть тот же самый компонент или отдельный.
### Примеры инструментов для HTTPS { #example-tools-for-https }
Некоторые инструменты, которые можно использовать как TLS Termination Proxy:
* Traefik
* Автоматически обновляет сертификаты ✨
* Caddy
* Автоматически обновляет сертификаты ✨
* Nginx
* С внешним компонентом (например, Certbot) для обновления сертификатов
* HAProxy
* С внешним компонентом (например, Certbot) для обновления сертификатов
* Kubernetes с Ingress Controller (например, Nginx)
* С внешним компонентом (например, cert-manager) для обновления сертификатов
* Обрабатывается внутри облачного провайдера как часть его услуг (см. ниже 👇)
Другой вариант — использовать **облачный сервис**, который возьмёт на себя больше задач, включая настройку HTTPS. Там могут быть ограничения или дополнительная стоимость и т.п., но в таком случае вам не придётся самим настраивать TLS Termination Proxy.
В следующих главах я покажу конкретные примеры.
---
Далее рассмотрим концепции, связанные с программой, которая запускает ваш реальный API (например, Uvicorn).
## Программа и процесс { #program-and-process }
Мы часто будем говорить о работающем "**процессе**", поэтому полезно чётко понимать, что это значит и чем отличается от "**программы**".
### Что такое программа { #what-is-a-program }
Словом **программа** обычно называют разные вещи:
* **Код**, который вы пишете, то есть **Python‑файлы**.
* **Файл**, который может быть **запущен** операционной системой, например: `python`, `python.exe` или `uvicorn`.
* Конкретную программу в момент, когда она **работает** в операционной системе, используя CPU и память. Это также называют **процессом**.
### Что такое процесс { #what-is-a-process }
Слово **процесс** обычно используют более конкретно — только для того, что реально выполняется в операционной системе (как в последнем пункте выше):
* Конкретная программа в момент, когда она **запущена** в операционной системе.
* Речь не о файле и не о коде, а **конкретно** о том, что **исполняется** и управляется операционной системой.
* Любая программа, любой код **могут что‑то делать** только когда **исполняются**, то есть когда есть **работающий процесс**.
* Процесс можно **завершить** (или «убить») вами или операционной системой. В этот момент он перестаёт выполняться и **больше ничего делать не может**.
* У каждого запущенного приложения на вашем компьютере есть свой процесс; у каждой программы, у каждого окна и т.д. Обычно одновременно **работает много процессов**, пока компьютер включён.
* Могут **одновременно** работать **несколько процессов** одной и той же **программы**.
Если вы посмотрите «диспетчер задач» или «системный монитор» (или аналогичные инструменты) в вашей операционной системе, то увидите множество работающих процессов.
Например, вы, скорее всего, увидите несколько процессов одного и того же браузера (Firefox, Chrome, Edge и т.д.). Обычно браузеры запускают один процесс на вкладку плюс дополнительные процессы.
<img class="shadow" src="/img/deployment/concepts/image01.png">
---
Теперь, когда мы понимаем разницу между **процессом** и **программой**, продолжим разговор о развёртываниях.
## Запуск при старте { #running-on-startup }
В большинстве случаев, создавая веб‑API, вы хотите, чтобы он **работал постоянно**, без перерывов, чтобы клиенты всегда могли к нему обратиться. Разве что у вас есть особые причины запускать его только при определённых условиях, но обычно вы хотите, чтобы он был постоянно запущен и **доступен**.
### На удалённом сервере { #in-a-remote-server }
Когда вы настраиваете удалённый сервер (облачный сервер, виртуальную машину и т.п.), самый простой вариант — вручную использовать `fastapi run` (он использует Uvicorn) или что‑то похожее, как вы делаете при локальной разработке.
Это будет работать и полезно **во время разработки**.
Но если соединение с сервером прервётся, **запущенный процесс**, скорее всего, завершится.
А если сервер перезагрузится (например, после обновлений или миграций у облачного провайдера), вы, вероятно, **даже не заметите этого**. Из‑за этого вы не узнаете, что нужно вручную перезапустить процесс — и ваш API просто будет «мёртв». 😱
### Автоматический запуск при старте { #run-automatically-on-startup }
Как правило, вы захотите, чтобы серверная программа (например, Uvicorn) запускалась автоматически при старте сервера и без **участия человека**, чтобы всегда был процесс, запущенный с вашим API (например, Uvicorn, запускающий ваше приложение FastAPI).
### Отдельная программа { #separate-program }
Чтобы этого добиться, обычно используют **отдельную программу**, которая гарантирует запуск вашего приложения при старте. Во многих случаях она также запускает и другие компоненты/приложения, например базу данных.
### Примеры инструментов для запуска при старте { #example-tools-to-run-at-startup }
Примеры инструментов, которые могут с этим справиться:
* Docker
* Kubernetes
* Docker Compose
* Docker в режиме Swarm (Swarm Mode)
* Systemd
* Supervisor
* Обработка внутри облачного провайдера как часть его услуг
* Прочие...
Более конкретные примеры будут в следующих главах.
## Перезапуски { #restarts }
Подобно тому как вы обеспечиваете запуск приложения при старте, вы, вероятно, захотите обеспечить его **перезапуск** после сбоев.
### Мы ошибаемся { #we-make-mistakes }
Мы, люди, постоянно совершаем **ошибки**. В программном обеспечении почти всегда есть **баги**, скрытые в разных местах. 🐛
И мы, как разработчики, продолжаем улучшать код — находим баги и добавляем новые возможности (иногда добавляя новые баги 😅).
### Небольшие ошибки обрабатываются автоматически { #small-errors-automatically-handled }
Создавая веб‑API с FastAPI, если в нашем коде возникает ошибка, FastAPI обычно «локализует» её в пределах одного запроса, который эту ошибку вызвал. 🛡
Клиент получит **500 Internal Server Error** для этого запроса, но приложение продолжит работать для последующих запросов, а не «упадёт» целиком.
### Большие ошибки — падения { #bigger-errors-crashes }
Тем не менее возможны случаи, когда код **роняет всё приложение**, приводя к сбою Uvicorn и Python. 💥
И вы, скорее всего, не захотите, чтобы приложение оставалось «мёртвым» из‑за ошибки в одном месте — вы захотите, чтобы оно **продолжало работать** хотя бы для *операций пути*, которые не сломаны.
### Перезапуск после падения { #restart-after-crash }
В случаях действительно серьёзных ошибок, которые роняют работающий **процесс**, вам понадобится внешний компонент, отвечающий за **перезапуск** процесса, как минимум пару раз...
/// tip | Совет
...Хотя если приложение **падает сразу же**, вероятно, нет смысла перезапускать его бесконечно. Но такие случаи вы, скорее всего, заметите во время разработки или как минимум сразу после развёртывания.
Давайте сосредоточимся на основных сценариях, когда в каких‑то конкретных ситуациях **в будущем** приложение может падать целиком, и при этом имеет смысл его перезапускать.
///
Скорее всего, вы захотите, чтобы перезапуском вашего приложения занимался **внешний компонент**, потому что к тому моменту Uvicorn и Python уже упали, и внутри того же кода вашего приложения сделать уже ничего нельзя.
### Примеры инструментов для автоматического перезапуска { #example-tools-to-restart-automatically }
В большинстве случаев тот же инструмент, который **запускает программу при старте**, умеет обрабатывать и автоматические **перезапуски**.
Например, это может быть:
* Docker
* Kubernetes
* Docker Compose
* Docker в режиме Swarm (Swarm Mode)
* Systemd
* Supervisor
* Обработка внутри облачного провайдера как часть его услуг
* Прочие...
## Репликация — процессы и память { #replication-processes-and-memory }
В приложении FastAPI, используя серверную программу (например, команду `fastapi`, которая запускает Uvicorn), запуск в **одном процессе** уже позволяет обслуживать нескольких клиентов одновременно.
Но во многих случаях вы захотите одновременно запустить несколько процессов‑воркеров.
### Несколько процессов — Воркеры { #multiple-processes-workers }
Если клиентов больше, чем способен обслужить один процесс (например, если виртуальная машина не слишком мощная), и на сервере есть **несколько ядер CPU**, вы можете запустить **несколько процессов** одного и того же приложения параллельно и распределять запросы между ними.
Когда вы запускаете **несколько процессов** одной и той же программы API, их обычно называют **воркерами**.
### Процессы‑воркеры и порты { #worker-processes-and-ports }
Помните из раздела [Об HTTPS](https.md){.internal-link target=_blank}, что на сервере только один процесс может слушать конкретную комбинацию порта и IP‑адреса?
Это по‑прежнему так.
Поэтому, чтобы одновременно работало **несколько процессов**, должен быть **один процесс, слушающий порт**, который затем каким‑то образом передаёт коммуникацию каждому воркер‑процессу.
### Память на процесс { #memory-per-process }
Когда программа загружает что‑то в память (например, модель машинного обучения в переменную или содержимое большого файла в переменную), всё это **потребляет часть памяти (RAM)** сервера.
И разные процессы обычно **не делят память**. Это значит, что у каждого процесса свои переменные и своя память. Если ваш код потребляет много памяти, то **каждый процесс** будет потреблять сопоставимый объём памяти.
### Память сервера { #server-memory }
Например, если ваш код загружает модель Машинного обучения размером **1 ГБ**, то при запуске одного процесса с вашим API он будет использовать как минимум 1 ГБ RAM. А если вы запустите **4 процесса** (4 воркера), каждый процесс будет использовать 1 ГБ RAM. Всего ваш API будет потреблять **4 ГБ RAM**.
И если у вашего удалённого сервера или виртуальной машины только 3 ГБ RAM, попытка загрузить более 4 ГБ вызовет проблемы. 🚨
### Несколько процессов — пример { #multiple-processes-an-example }
В этом примере есть **процесс‑менеджер**, который запускает и контролирует два **процесса‑воркера**.
Процесс‑менеджер, вероятно, будет тем, кто слушает **порт** на IP. И он будет передавать всю коммуникацию воркер‑процессам.
Эти воркеры будут запускать ваше приложение, выполнять основные вычисления для получения **запроса** и возврата **ответа**, и загружать всё, что вы кладёте в переменные, в RAM.
<img src="/img/deployment/concepts/process-ram.drawio.svg">
Конечно, на той же машине помимо вашего приложения, скорее всего, будут работать и **другие процессы**.
Интересная деталь: процент **использования CPU** каждым процессом со временем может сильно **меняться**, но **память (RAM)** обычно остаётся более‑менее **стабильной**.
Если у вас API, который каждый раз выполняет сопоставимый объём вычислений, и у вас много клиентов, то **загрузка процессора**, вероятно, *тоже будет стабильной* (вместо того, чтобы быстро и постоянно «скакать»).
### Примеры инструментов и стратегий репликации { #examples-of-replication-tools-and-strategies }
Есть несколько подходов для достижения этого, и я расскажу больше о конкретных стратегиях в следующих главах, например, говоря о Docker и контейнерах.
Главное ограничение: должен быть **один** компонент, который обрабатывает **порт** на **публичном IP**. И у него должен быть способ **передавать** коммуникацию реплицированным **процессам/воркерам**.
Некоторые возможные комбинации и стратегии:
* **Uvicorn** с `--workers`
* Один **процесс‑менеджер** Uvicorn будет слушать **IP** и **порт** и запускать **несколько процессов‑воркеров Uvicorn**.
* **Kubernetes** и другие распределённые **контейнерные системы**
* Некий компонент на уровне **Kubernetes** будет слушать **IP** и **порт**. Репликация достигается с помощью **нескольких контейнеров**, в каждом из которых работает **один процесс Uvicorn**.
* **Облачные сервисы**, которые берут это на себя
* Облачный сервис, скорее всего, **возьмёт репликацию на себя**. Он, возможно, позволит указать **процесс для запуска** или **образ контейнера**. В любом случае это, скорее всего, будет **один процесс Uvicorn**, а сервис займётся его репликацией.
/// tip | Совет
Не беспокойтесь, если некоторые пункты про **контейнеры**, Docker или Kubernetes пока кажутся неочевидными.
Я расскажу больше про образы контейнеров, Docker, Kubernetes и т.п. в следующей главе: [FastAPI внутри контейнеров — Docker](docker.md){.internal-link target=_blank}.
///
## Предварительные шаги перед запуском { #previous-steps-before-starting }
Во многих случаях вы захотите выполнить некоторые шаги **перед запуском** приложения.
Например, запустить **миграции базы данных**.
Но чаще всего эти шаги нужно выполнять только **один раз**.
Поэтому вы захотите иметь **один процесс**, который выполнит эти **предварительные шаги**, прежде чем запускать приложение.
И вам нужно будет убедиться, что это делает один процесс **даже** если потом вы запускаете **несколько процессов** (несколько воркеров) самого приложения. Если эти шаги выполнят **несколько процессов**, они **дублируют** работу, запустив её **параллельно**, и, если речь о чём‑то деликатном (например, миграции БД), это может вызвать конфликты.
Конечно, бывают случаи, когда нет проблем, если предварительные шаги выполняются несколько раз — тогда всё проще.
/// tip | Совет
Также учтите, что в зависимости от вашей схемы развёртывания в некоторых случаях **предварительные шаги могут вовсе не требоваться**.
Тогда об этом можно не беспокоиться. 🤷
///
### Примеры стратегий для предварительных шагов { #examples-of-previous-steps-strategies }
Это будет **сильно зависеть** от того, как вы **развёртываете систему**, как запускаете программы, обрабатываете перезапуски и т.д.
Некоторые возможные идеи:
* «Init Container» в Kubernetes, который запускается перед контейнером с приложением
* Bash‑скрипт, который выполняет предварительные шаги, а затем запускает приложение
* При этом всё равно нужен способ запускать/перезапускать *этот* bash‑скрипт, обнаруживать ошибки и т.п.
/// tip | Совет
Я приведу более конкретные примеры с контейнерами в следующей главе: [FastAPI внутри контейнеров — Docker](docker.md){.internal-link target=_blank}.
///
## Использование ресурсов { #resource-utilization }
Ваш сервер(а) — это **ресурс**, который ваши программы могут потреблять или **использовать**: время вычислений на CPU и доступную оперативную память (RAM).
Какую долю системных ресурсов вы хотите потреблять/использовать? Можно подумать «немного», но на практике вы, скорее всего, захотите потреблять **максимум без падений**.
Если вы платите за 3 сервера, но используете лишь малую часть их RAM и CPU, вы, вероятно, **тратите деньги впустую** 💸 и **электроэнергию серверов** 🌎 и т.п.
В таком случае лучше иметь 2 сервера и использовать более высокий процент их ресурсов (CPU, память, диск, сетевую полосу и т.д.).
С другой стороны, если у вас 2 сервера и вы используете **100% их CPU и RAM**, в какой‑то момент один процесс попросит больше памяти, и сервер начнёт использовать диск как «память» (что в тысячи раз медленнее) или даже **упадёт**. Или процессу понадобятся вычисления, но ему придётся ждать освобождения CPU.
В таком случае лучше добавить **ещё один сервер** и запустить часть процессов на нём, чтобы у всех было **достаточно RAM и времени CPU**.
Также возможен **всплеск** использования вашего API: он мог «взорваться» по популярности, или какие‑то сервисы/боты начали его активно использовать. На такие случаи стоит иметь запас ресурсов.
Можно задать **целевое значение**, например **между 50% и 90%** использования ресурсов. Скорее всего, именно эти вещи вы будете измерять и на их основе настраивать развёртывание.
Можно использовать простые инструменты вроде `htop`, чтобы смотреть загрузку CPU и RAM на сервере или по процессам. Или более сложные распределённые системы мониторинга.
## Резюме { #recap }
Здесь вы прочитали о некоторых основных концепциях, которые, вероятно, стоит учитывать при выборе способа развёртывания приложения:
* Безопасность — HTTPS
* Запуск при старте
* Перезапуски
* Репликация (количество запущенных процессов)
* Память
* Предварительные шаги перед запуском
Понимание этих идей и того, как их применять, даст вам интуицию, необходимую для принятия решений при настройке и доработке ваших развёртываний. 🤓
В следующих разделах я приведу более конкретные примеры возможных стратегий. 🚀
+620
View File
@@ -0,0 +1,620 @@
# FastAPI в контейнерах — Docker { #fastapi-in-containers-docker }
При развёртывании приложений FastAPI распространённый подход — собирать **образ контейнера на Linux**. Обычно это делают с помощью <a href="https://www.docker.com/" class="external-link" target="_blank">**Docker**</a>. Затем такой образ контейнера можно развернуть несколькими способами.
Использование Linux-контейнеров даёт ряд преимуществ: **безопасность**, **воспроизводимость**, **простоту** и другие.
/// tip | Подсказка
Нет времени и вы уже знакомы с этим? Перейдите к [`Dockerfile` ниже 👇](#build-a-docker-image-for-fastapi).
///
<details>
<summary>Предпросмотр Dockerfile 👀</summary>
```Dockerfile
FROM python:3.9
WORKDIR /code
COPY ./requirements.txt /code/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
COPY ./app /code/app
CMD ["fastapi", "run", "app/main.py", "--port", "80"]
# Если запускаете за прокси, например Nginx или Traefik, добавьте --proxy-headers
# CMD ["fastapi", "run", "app/main.py", "--port", "80", "--proxy-headers"]
```
</details>
## Что такое контейнер { #what-is-a-container }
Контейнеры (в основном Linux-контейнеры) — это очень **легковесный** способ упаковать приложения вместе со всеми их зависимостями и необходимыми файлами, изолировав их от других контейнеров (других приложений или компонентов) в той же системе.
Linux-контейнеры запускаются, используя то же ядро Linux хоста (машины, виртуальной машины, облачного сервера и т.п.). Это означает, что они очень легковесные (по сравнению с полноценными виртуальными машинами, эмулирующими целую операционную систему).
Таким образом, контейнеры потребляют **малое количество ресурсов**, сопоставимое с запуском процессов напрямую (виртуальная машина потребовала бы намного больше ресурсов).
У контейнеров также есть собственные **изолированные** выполняемые процессы (обычно всего один процесс), файловая система и сеть, что упрощает развёртывание, безопасность, разработку и т.д.
## Что такое образ контейнера { #what-is-a-container-image }
**Контейнер** запускается из **образа контейнера**.
Образ контейнера — это **статическая** версия всех файлов, переменных окружения и команды/программы по умолчанию, которые должны присутствовать в контейнере. Здесь **статическая** означает, что **образ** не запущен, он не выполняется — это только упакованные файлы и метаданные.
В противоположность «**образу контейнера**» (хранящему статическое содержимое), «**контейнер**» обычно означает запущенный экземпляр, то, что **выполняется**.
Когда **контейнер** запущен (на основе **образа контейнера**), он может создавать или изменять файлы, переменные окружения и т.д.. Эти изменения существуют только внутри контейнера и не сохраняются в исходном образе контейнера (не записываются на диск).
Образ контейнера можно сравнить с **файлами программы**, например `python` и каким-то файлом `main.py`.
А сам **контейнер** (в отличие от **образа контейнера**) — это фактически запущенный экземпляр образа, сопоставимый с **процессом**. По сути, контейнер работает только тогда, когда в нём есть **запущенный процесс** (и обычно это один процесс). Контейнер останавливается, когда в нём не остаётся запущенных процессов.
## Образы контейнеров { #container-images }
Docker — один из основных инструментов для создания и управления **образами контейнеров** и **контейнерами**.
Существует публичный <a href="https://hub.docker.com/" class="external-link" target="_blank">Docker Hub</a> с готовыми **официальными образами** для многих инструментов, окружений, баз данных и приложений.
Например, есть официальный <a href="https://hub.docker.com/_/python" class="external-link" target="_blank">образ Python</a>.
А также множество образов для разных вещей, например баз данных:
* <a href="https://hub.docker.com/_/postgres" class="external-link" target="_blank">PostgreSQL</a>
* <a href="https://hub.docker.com/_/mysql" class="external-link" target="_blank">MySQL</a>
* <a href="https://hub.docker.com/_/mongo" class="external-link" target="_blank">MongoDB</a>
* <a href="https://hub.docker.com/_/redis" class="external-link" target="_blank">Redis</a>, и т.д.
Используя готовые образы, очень легко **комбинировать** разные инструменты и использовать их. Например, чтобы попробовать новую базу данных. В большинстве случаев можно воспользоваться **официальными образами** и просто настроить их через переменные окружения.
Таким образом, во многих случаях вы можете изучить контейнеры и Docker и переиспользовать эти знания с множеством различных инструментов и компонентов.
Например, вы можете запустить **несколько контейнеров**: с базой данных, Python-приложением, веб-сервером с фронтендом на React и связать их через внутреннюю сеть.
Все системы управления контейнерами (такие как Docker или Kubernetes) имеют интегрированные возможности для такого сетевого взаимодействия.
## Контейнеры и процессы { #containers-and-processes }
**Образ контейнера** обычно включает в свои метаданные программу или команду по умолчанию, которую следует запускать при старте **контейнера**, а также параметры, передаваемые этой программе. Это очень похоже на запуск команды в терминале.
Когда **контейнер** стартует, он выполняет указанную команду/программу (хотя вы можете переопределить это и запустить другую команду/программу).
Контейнер работает до тех пор, пока работает его **главный процесс** (команда или программа).
Обычно в контейнере есть **один процесс**, но главный процесс может запускать подпроцессы, и тогда в том же контейнере будет **несколько процессов**.
Нельзя иметь работающий контейнер без **хотя бы одного запущенного процесса**. Если главный процесс останавливается, контейнер останавливается.
## Создать Docker-образ для FastAPI { #build-a-docker-image-for-fastapi }
Итак, давайте что-нибудь соберём! 🚀
Я покажу, как собрать **Docker-образ** для FastAPI **с нуля** на основе **официального образа Python**.
Именно так стоит делать в **большинстве случаев**, например:
* При использовании **Kubernetes** или похожих инструментов
* При запуске на **Raspberry Pi**
* При использовании облачного сервиса, который запускает для вас образ контейнера и т.п.
### Зависимости пакетов { #package-requirements }
Обычно **зависимости** вашего приложения описаны в каком-то файле.
Конкретный формат зависит в основном от инструмента, которым вы **устанавливаете** эти зависимости.
Чаще всего используется файл `requirements.txt` с именами пакетов и их версиями по одному на строку.
Разумеется, вы будете придерживаться тех же идей, что описаны здесь: [О версиях FastAPI](versions.md){.internal-link target=_blank}, чтобы задать диапазоны версий.
Например, ваш `requirements.txt` может выглядеть так:
```
fastapi[standard]>=0.113.0,<0.114.0
pydantic>=2.7.0,<3.0.0
```
И обычно вы установите эти зависимости командой `pip`, например:
<div class="termy">
```console
$ pip install -r requirements.txt
---> 100%
Successfully installed fastapi pydantic
```
</div>
/// info | Информация
Существуют и другие форматы и инструменты для описания и установки зависимостей.
///
### Создать код **FastAPI** { #create-the-fastapi-code }
* Создайте директорию `app` и перейдите в неё.
* Создайте пустой файл `__init__.py`.
* Создайте файл `main.py` со следующим содержимым:
```Python
from typing import Union
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
@app.get("/items/{item_id}")
def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}
```
### Dockerfile { #dockerfile }
Теперь в той же директории проекта создайте файл `Dockerfile`:
```{ .dockerfile .annotate }
# (1)!
FROM python:3.9
# (2)!
WORKDIR /code
# (3)!
COPY ./requirements.txt /code/requirements.txt
# (4)!
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
# (5)!
COPY ./app /code/app
# (6)!
CMD ["fastapi", "run", "app/main.py", "--port", "80"]
```
1. Начинаем с официального базового образа Python.
2. Устанавливаем текущую рабочую директорию в `/code`.
Здесь мы разместим файл `requirements.txt` и директорию `app`.
3. Копируем файл с зависимостями в директорию `/code`.
Сначала копируйте **только** файл с зависимостями, не остальной код.
Так как этот файл **меняется нечасто**, Docker определит это и использует **кэш** на этом шаге, что позволит использовать кэш и на следующем шаге.
4. Устанавливаем зависимости из файла с требованиями.
Опция `--no-cache-dir` указывает `pip` не сохранять загруженные пакеты локально, т.к. это нужно только если `pip` будет запускаться снова для установки тех же пакетов, а при работе с контейнерами это обычно не требуется.
/// note | Заметка
`--no-cache-dir` относится только к `pip` и не имеет отношения к Docker или контейнерам.
///
Опция `--upgrade` указывает `pip` обновлять пакеты, если они уже установлены.
Поскольку предыдущий шаг с копированием файла может быть обработан **кэшем Docker**, этот шаг также **использует кэш Docker**, когда это возможно.
Использование кэша на этом шаге **сэкономит** вам много **времени** при повторных сборках образа во время разработки, вместо того чтобы **загружать и устанавливать** все зависимости **каждый раз**.
5. Копируем директорию `./app` внутрь директории `/code`.
Так как здесь весь код, который **меняется чаще всего**, кэш Docker **вряд ли** будет использоваться для этого шагa или **последующих шагов**.
Поэтому важно разместить этот шаг **ближе к концу** `Dockerfile`, чтобы оптимизировать время сборки образа контейнера.
6. Указываем **команду** для запуска `fastapi run`, под капотом используется Uvicorn.
`CMD` принимает список строк, каждая из которых — это то, что вы бы ввели в командной строке, разделяя пробелами.
Эта команда будет выполнена из **текущей рабочей директории**, той самой `/code`, которую вы задали выше `WORKDIR /code`.
/// tip | Подсказка
Посмотрите, что делает каждая строка, кликнув по номеру рядом со строкой. 👆
///
/// warning | Предупреждение
Всегда используйте **exec-форму** инструкции `CMD`, как описано ниже.
///
#### Используйте `CMD` — exec-форма { #use-cmd-exec-form }
Инструкцию Docker <a href="https://docs.docker.com/reference/dockerfile/#cmd" class="external-link" target="_blank">`CMD`</a> можно писать в двух формах:
✅ **Exec**-форма:
```Dockerfile
# ✅ Делайте так
CMD ["fastapi", "run", "app/main.py", "--port", "80"]
```
⛔️ **Shell**-форма:
```Dockerfile
# ⛔️ Не делайте так
CMD fastapi run app/main.py --port 80
```
Обязательно используйте **exec**-форму, чтобы FastAPI мог корректно завершаться и чтобы срабатывали [события lifespan](../advanced/events.md){.internal-link target=_blank}.
Подробнее об этом читайте в <a href="https://docs.docker.com/reference/dockerfile/#shell-and-exec-form" class="external-link" target="_blank">документации Docker о shell- и exec-формах</a>.
Это особенно заметно при использовании `docker compose`. См. раздел FAQ Docker Compose с техническими подробностями: <a href="https://docs.docker.com/compose/faq/#why-do-my-services-take-10-seconds-to-recreate-or-stop" class="external-link" target="_blank">Почему мои сервисы пересоздаются или останавливаются 10 секунд?</a>.
#### Структура директорий { #directory-structure }
Теперь у вас должна быть такая структура:
```
.
├── app
│   ├── __init__.py
│ └── main.py
├── Dockerfile
└── requirements.txt
```
#### За прокси-сервером TLS терминации { #behind-a-tls-termination-proxy }
Если вы запускаете контейнер за прокси-сервером завершения TLS (балансировщиком нагрузки), таким как Nginx или Traefik, добавьте опцию `--proxy-headers`. Это сообщит Uvicorn (через FastAPI CLI), что приложение работает за HTTPS и можно доверять соответствующим заголовкам.
```Dockerfile
CMD ["fastapi", "run", "app/main.py", "--proxy-headers", "--port", "80"]
```
#### Кэш Docker { #docker-cache }
В этом `Dockerfile` есть важная хитрость: мы сначала копируем **только файл с зависимостями**, а не весь код. Вот зачем.
```Dockerfile
COPY ./requirements.txt /code/requirements.txt
```
Docker и подобные инструменты **строят** образы контейнеров **инкрементально**, добавляя **слой за слоем**, начиная с первой строки `Dockerfile` и добавляя любые файлы, создаваемые каждой инструкцией `Dockerfile`.
Docker и подобные инструменты также используют **внутренний кэш** при сборке образа: если файл не изменился с момента предыдущей сборки, будет **переиспользован слой**, созданный в прошлый раз, вместо повторного копирования файла и создания нового слоя с нуля.
Само по себе избегание копирования всех файлов не всегда даёт много, но благодаря использованию кэша на этом шаге Docker сможет **использовать кэш и на следующем шаге**. Например, на шаге установки зависимостей:
```Dockerfile
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
```
Файл с зависимостями **меняется нечасто**. Поэтому, копируя только его, Docker сможет **использовать кэш** для этого шага.
А затем Docker сможет **использовать кэш и на следующем шаге**, где скачиваются и устанавливаются зависимости. Здесь мы как раз **экономим много времени**. ✨ ...и не скучаем в ожидании. 😪😆
Скачивание и установка зависимостей **может занять минуты**, но использование **кэша** — **секунды**.
Поскольку во время разработки вы будете пересобирать образ снова и снова, чтобы проверить изменения в коде, суммарно это сэкономит немало времени.
Затем, ближе к концу `Dockerfile`, мы копируем весь код. Так как он **меняется чаще всего**, мы ставим этот шаг в конец, потому что почти всегда всё, что после него, уже не сможет использовать кэш.
```Dockerfile
COPY ./app /code/app
```
### Собрать Docker-образ { #build-the-docker-image }
Теперь, когда все файлы на месте, соберём образ контейнера.
* Перейдите в директорию проекта (где ваш `Dockerfile` и директория `app`).
* Соберите образ FastAPI:
<div class="termy">
```console
$ docker build -t myimage .
---> 100%
```
</div>
/// tip | Подсказка
Обратите внимание на точку `.` в конце — это то же самое, что `./`. Так мы указываем Docker, из какой директории собирать образ контейнера.
В данном случае это текущая директория (`.`).
///
### Запустить Docker-контейнер { #start-the-docker-container }
* Запустите контейнер на основе вашего образа:
<div class="termy">
```console
$ docker run -d --name mycontainer -p 80:80 myimage
```
</div>
## Проверка { #check-it }
Проверьте работу по адресу вашего Docker-хоста, например: <a href="http://192.168.99.100/items/5?q=somequery" class="external-link" target="_blank">http://192.168.99.100/items/5?q=somequery</a> или <a href="http://127.0.0.1/items/5?q=somequery" class="external-link" target="_blank">http://127.0.0.1/items/5?q=somequery</a> (или аналогичный URL вашего Docker-хоста).
Вы увидите что-то вроде:
```JSON
{"item_id": 5, "q": "somequery"}
```
## Интерактивная документация API { #interactive-api-docs }
Теперь зайдите на <a href="http://192.168.99.100/docs" class="external-link" target="_blank">http://192.168.99.100/docs</a> или <a href="http://127.0.0.1/docs" class="external-link" target="_blank">http://127.0.0.1/docs</a> (или аналогичный URL вашего Docker-хоста).
Вы увидите автоматическую интерактивную документацию API (на базе <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>):
![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png)
## Альтернативная документация API { #alternative-api-docs }
Также можно открыть <a href="http://192.168.99.100/redoc" class="external-link" target="_blank">http://192.168.99.100/redoc</a> или <a href="http://127.0.0.1/redoc" class="external-link" target="_blank">http://127.0.0.1/redoc</a> (или аналогичный URL вашего Docker-хоста).
Вы увидите альтернативную автоматическую документацию (на базе <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>):
![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png)
## Собрать Docker-образ для однофайлового FastAPI { #build-a-docker-image-with-a-single-file-fastapi }
Если ваше приложение FastAPI — один файл, например `main.py` без директории `./app`, структура файлов может быть такой:
```
.
├── Dockerfile
├── main.py
└── requirements.txt
```
Тогда в `Dockerfile` нужно изменить пути копирования:
```{ .dockerfile .annotate hl_lines="10 13" }
FROM python:3.9
WORKDIR /code
COPY ./requirements.txt /code/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
# (1)!
COPY ./main.py /code/
# (2)!
CMD ["fastapi", "run", "main.py", "--port", "80"]
```
1. Копируем файл `main.py` напрямую в `/code` (без директории `./app`).
2. Используем `fastapi run` для запуска приложения из одного файла `main.py`.
Когда вы передаёте файл в `fastapi run`, он автоматически определит, что это одиночный файл, а не часть пакета, и поймёт, как его импортировать и запустить ваше FastAPI-приложение. 😎
## Концепции развертывания { #deployment-concepts }
Ещё раз рассмотрим [концепции развертывания](concepts.md){.internal-link target=_blank} применительно к контейнерам.
Контейнеры главным образом упрощают **сборку и развёртывание** приложения, но не навязывают конкретный подход к этим **концепциям развертывания**, и существует несколько стратегий.
**Хорошая новость** в том, что при любой стратегии есть способ охватить все концепции развертывания. 🎉
Рассмотрим эти **концепции развертывания** в терминах контейнеров:
* HTTPS
* Запуск при старте
* Перезапуски
* Репликация (количество запущенных процессов)
* Память
* Предварительные шаги перед запуском
## HTTPS { #https }
Если мы рассматриваем только **образ контейнера** для приложения FastAPI (и далее запущенный **контейнер**), то HTTPS обычно обрабатывается **внешним** инструментом.
Это может быть другой контейнер, например с <a href="https://traefik.io/" class="external-link" target="_blank">Traefik</a>, который берёт на себя **HTTPS** и **автоматическое** получение **сертификатов**.
/// tip | Подсказка
У Traefik есть интеграции с Docker, Kubernetes и другими, поэтому очень легко настроить и сконфигурировать HTTPS для ваших контейнеров.
///
В качестве альтернативы HTTPS может быть реализован как сервис облачного провайдера (при этом приложение всё равно работает в контейнере).
## Запуск при старте и перезапуски { #running-on-startup-and-restarts }
Обычно есть другой инструмент, отвечающий за **запуск и работу** вашего контейнера.
Это может быть сам **Docker**, **Docker Compose**, **Kubernetes**, **облачный сервис** и т.п.
В большинстве (или во всех) случаев есть простая опция, чтобы включить запуск контейнера при старте системы и перезапуски при сбоях. Например, в Docker это опция командной строки `--restart`.
Без контейнеров обеспечить запуск при старте и перезапуски может быть сложно. Но при **работе с контейнерами** в большинстве случаев этот функционал доступен по умолчанию. ✨
## Репликация — количество процессов { #replication-number-of-processes }
Если у вас есть <abbr title="Группа машин, настроенных так, чтобы быть соединенными и работать вместе определенным образом.">кластер</abbr> машин с **Kubernetes**, Docker Swarm Mode, Nomad или другой похожей системой для управления распределёнными контейнерами на нескольких машинах, скорее всего вы будете **управлять репликацией** на **уровне кластера**, а не использовать **менеджер процессов** (например, Uvicorn с воркерами) в каждом контейнере.
Одна из таких систем управления распределёнными контейнерами, как Kubernetes, обычно имеет встроенный способ управлять **репликацией контейнеров**, поддерживая **балансировку нагрузки** для входящих запросов — всё это на **уровне кластера**.
В таких случаях вы, скорее всего, захотите собрать **Docker-образ с нуля**, как [описано выше](#dockerfile), установить зависимости и запускать **один процесс Uvicorn** вместо множества воркеров Uvicorn.
### Балансировщик нагрузки { #load-balancer }
При использовании контейнеров обычно есть компонент, **слушающий главный порт**. Это может быть другой контейнер — **прокси завершения TLS** для обработки **HTTPS** или похожий инструмент.
Поскольку этот компонент принимает **нагрузку** запросов и распределяет её между воркерами **сбалансированно**, его часто называют **балансировщиком нагрузки**.
/// tip | Подсказка
Тот же компонент **прокси завершения TLS**, который обрабатывает HTTPS, скорее всего также будет **балансировщиком нагрузки**.
///
При работе с контейнерами система, которую вы используете для запуска и управления ими, уже имеет внутренние средства для передачи **сетевого взаимодействия** (например, HTTP-запросов) от **балансировщика нагрузки** (который также может быть **прокси завершения TLS**) к контейнеру(-ам) с вашим приложением.
### Один балансировщик — несколько контейнеров-воркеров { #one-load-balancer-multiple-worker-containers }
При работе с **Kubernetes** или похожими системами управления распределёнными контейнерами их внутренние механизмы сети позволяют одному **балансировщику нагрузки**, слушающему главный **порт**, передавать запросы в **несколько контейнеров**, где запущено ваше приложение.
Каждый такой контейнер с вашим приложением обычно имеет **только один процесс** (например, процесс Uvicorn с вашим приложением FastAPI). Все они — **одинаковые контейнеры**, запускающие одно и то же, но у каждого свой процесс, память и т.п. Так вы используете **параллелизм** по **разным ядрам** CPU или даже **разным машинам**.
Система распределённых контейнеров с **балансировщиком нагрузки** будет **распределять запросы** между контейнерами с вашим приложением **по очереди**. То есть каждый запрос может обрабатываться одним из нескольких **реплицированных контейнеров**.
Обычно такой **балансировщик нагрузки** может также обрабатывать запросы к *другим* приложениям в вашем кластере (например, к другому домену или под другим префиксом пути URL) и направлять их к нужным контейнерам этого *другого* приложения.
### Один процесс на контейнер { #one-process-per-container }
В таком сценарии, скорее всего, вы захотите иметь **один (Uvicorn) процесс на контейнер**, так как репликация уже управляется на уровне кластера.
Поэтому в контейнере **не нужно** поднимать несколько воркеров, например через опцию командной строки `--workers`. Нужен **один процесс Uvicorn** на контейнер (но, возможно, несколько контейнеров).
Наличие отдельного менеджера процессов внутри контейнера (как при нескольких воркерах) только добавит **лишнюю сложность**, которую, вероятно, уже берёт на себя ваша кластерная система.
### Контейнеры с несколькими процессами и особые случаи { #containers-with-multiple-processes-and-special-cases }
Конечно, есть **особые случаи**, когда может понадобиться **контейнер** с несколькими **воркерами Uvicorn** внутри.
В таких случаях вы можете использовать опцию командной строки `--workers`, чтобы указать нужное количество воркеров:
```{ .dockerfile .annotate }
FROM python:3.9
WORKDIR /code
COPY ./requirements.txt /code/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
COPY ./app /code/app
# (1)!
CMD ["fastapi", "run", "app/main.py", "--port", "80", "--workers", "4"]
```
1. Здесь мы используем опцию `--workers`, чтобы установить число воркеров равным 4.
Примеры, когда это может быть уместно:
#### Простое приложение { #a-simple-app }
Вам может понадобиться менеджер процессов в контейнере, если приложение **достаточно простое**, чтобы запускаться на **одном сервере**, а не в кластере.
#### Docker Compose { #docker-compose }
Вы можете развёртывать на **одном сервере** (не кластере) с **Docker Compose**, и у вас не будет простого способа управлять репликацией контейнеров (в Docker Compose), сохраняя общую сеть и **балансировку нагрузки**.
Тогда вы можете захотеть **один контейнер** с **менеджером процессов**, который запускает **несколько воркеров** внутри.
---
Главное — **ни одно** из этих правил не является **строго обязательным**. Используйте эти идеи, чтобы **оценить свой конкретный случай** и решить, какой подход лучше для вашей системы, учитывая:
* Безопасность — HTTPS
* Запуск при старте
* Перезапуски
* Репликацию (количество запущенных процессов)
* Память
* Предварительные шаги перед запуском
## Память { #memory }
Если вы запускаете **один процесс на контейнер**, у каждого контейнера будет более-менее чётко определённый, стабильный и ограниченный объём потребляемой памяти (контейнеров может быть несколько при репликации).
Затем вы можете задать такие же лимиты и требования по памяти в конфигурации вашей системы управления контейнерами (например, в **Kubernetes**). Так система сможет **реплицировать контейнеры** на **доступных машинах**, учитывая объём необходимой памяти и доступной памяти в машинах кластера.
Если приложение **простое**, это, вероятно, **не будет проблемой**, и жёсткие лимиты памяти можно не указывать. Но если вы **используете много памяти** (например, с моделями **Машинного обучения**), проверьте, сколько памяти потребляется, и отрегулируйте **число контейнеров** на **каждой машине** (и, возможно, добавьте машины в кластер).
Если вы запускаете **несколько процессов в контейнере**, нужно убедиться, что их суммарное потребление **не превысит доступную память**.
## Предварительные шаги перед запуском и контейнеры { #previous-steps-before-starting-and-containers }
Если вы используете контейнеры (например, Docker, Kubernetes), есть два основных подхода.
### Несколько контейнеров { #multiple-containers }
Если у вас **несколько контейнеров**, и, вероятно, каждый запускает **один процесс** (например, в кластере **Kubernetes**), то вы, скорее всего, захотите иметь **отдельный контейнер**, выполняющий **предварительные шаги** в одном контейнере и одном процессе **до** запуска реплицированных контейнеров-воркеров.
/// info | Информация
Если вы используете Kubernetes, это, вероятно, будет <a href="https://kubernetes.io/docs/concepts/workloads/pods/init-containers/" class="external-link" target="_blank">Init Container</a>.
///
Если в вашем случае нет проблемы с тем, чтобы выполнять эти предварительные шаги **многократно и параллельно** (например, вы не запускаете миграции БД, а только проверяете готовность БД), вы можете просто выполнить их в каждом контейнере прямо перед стартом основного процесса.
### Один контейнер { #single-container }
Если у вас простая схема с **одним контейнером**, который затем запускает несколько **воркеров** (или один процесс), можно выполнить подготовительные шаги в этом же контейнере непосредственно перед запуском процесса с приложением.
### Базовый Docker-образ { #base-docker-image }
Ранее существовал официальный Docker-образ FastAPI: <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker" class="external-link" target="_blank">tiangolo/uvicorn-gunicorn-fastapi</a>. Сейчас он помечен как устаревший. ⛔️
Скорее всего, вам **не стоит** использовать этот базовый образ (или какой-либо аналогичный).
Если вы используете **Kubernetes** (или другое) и уже настраиваете **репликацию** на уровне кластера через несколько **контейнеров**, в этих случаях лучше **собрать образ с нуля**, как описано выше: [Создать Docker-образ для FastAPI](#build-a-docker-image-for-fastapi).
А если вам нужны несколько воркеров, просто используйте опцию командной строки `--workers`.
/// note | Технические подробности
Этот Docker-образ был создан в то время, когда Uvicorn не умел управлять и перезапускать «упавших» воркеров, и приходилось использовать Gunicorn вместе с Uvicorn, что добавляло заметную сложность, лишь бы Gunicorn управлял и перезапускал воркеров Uvicorn.
Но теперь, когда Uvicorn (и команда `fastapi`) поддерживают `--workers`, нет причин использовать базовый Docker-образ вместо сборки своего (кода получается примерно столько же 😅).
///
## Развёртывание образа контейнера { #deploy-the-container-image }
После того как у вас есть образ контейнера (Docker), его можно развёртывать несколькими способами.
Например:
* С **Docker Compose** на одном сервере
* В кластере **Kubernetes**
* В кластере Docker Swarm Mode
* С другим инструментом, например Nomad
* С облачным сервисом, который принимает ваш образ контейнера и разворачивает его
## Docker-образ с `uv` { #docker-image-with-uv }
Если вы используете <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">uv</a> для установки и управления проектом, следуйте их <a href="https://docs.astral.sh/uv/guides/integration/docker/" class="external-link" target="_blank">руководству по Docker для uv</a>.
## Резюме { #recap }
Используя системы контейнеризации (например, **Docker** и **Kubernetes**), довольно просто закрыть все **концепции развертывания**:
* HTTPS
* Запуск при старте
* Перезапуски
* Репликация (количество запущенных процессов)
* Память
* Предварительные шаги перед запуском
В большинстве случаев вы, вероятно, не захотите использовать какой-либо базовый образ, а вместо этого **соберёте образ контейнера с нуля** на основе официального Docker-образа Python.
Заботясь о **порядке** инструкций в `Dockerfile`и используя **кэш Docker**, вы можете **минимизировать время сборки**, чтобы повысить продуктивность (и не скучать). 😎
+231
View File
@@ -0,0 +1,231 @@
# Об HTTPS { #about-https }
Легко предположить, что HTTPS — это что-то, что просто «включено» или нет.
Но на самом деле всё гораздо сложнее.
/// tip | Совет
Если вы торопитесь или вам это не важно, переходите к следующим разделам с пошаговыми инструкциями по настройке всего разными способами.
///
Чтобы **изучить основы HTTPS** с точки зрения пользователя, загляните на <a href="https://howhttps.works/" class="external-link" target="_blank">https://howhttps.works/</a>.
Теперь, со стороны **разработчика**, вот несколько вещей, которые стоит держать в голове, размышляя об HTTPS:
* Для HTTPS **серверу** нужно **иметь «сертификаты»**, сгенерированные **третьей стороной**.
* Эти сертификаты на самом деле **приобретаются** у третьей стороны, а не «генерируются».
* У сертификатов есть **срок действия**.
* Они **истекают**.
* После этого их нужно **обновлять**, то есть **получать заново** у третьей стороны.
* Шифрование соединения происходит на **уровне TCP**.
* Это на один уровень **ниже HTTP**.
* Поэтому **сертификаты и шифрование** обрабатываются **до HTTP**.
* **TCP не знает о «доменах»**. Только об IP-адресах.
* Информация о **конкретном домене** передаётся в **данных HTTP**.
* **HTTPS-сертификаты** «сертифицируют» **определённый домен**, но протокол и шифрование происходят на уровне TCP, **до того как** становится известен домен, с которым идёт работа.
* **По умолчанию** это означает, что вы можете иметь **лишь один HTTPS-сертификат на один IP-адрес**.
* Неважно, насколько мощный у вас сервер или насколько маленькие приложения на нём работают.
* Однако у этого есть **решение**.
* Есть **расширение** протокола **TLS** (того самого, что занимается шифрованием на уровне TCP, до HTTP) под названием **<a href="https://en.wikipedia.org/wiki/Server_Name_Indication" class="external-link" target="_blank"><abbr title="Server Name Indication – Указание имени сервера">SNI</abbr></a>**.
* Это расширение SNI позволяет одному серверу (с **одним IP-адресом**) иметь **несколько HTTPS-сертификатов** и обслуживать **несколько HTTPS-доменов/приложений**.
* Чтобы это работало, **один** компонент (программа), запущенный на сервере и слушающий **публичный IP-адрес**, должен иметь **все HTTPS-сертификаты** на этом сервере.
* **После** установления защищённого соединения, протокол обмена данными — **всё ещё HTTP**.
* Содержимое **зашифровано**, несмотря на то, что оно отправляется по **протоколу HTTP**.
Обычно на сервере (машине, хосте и т.п.) запускают **одну программу/HTTP‑сервер**, которая **управляет всей частью, связанной с HTTPS**: принимает **зашифрованные HTTPS-запросы**, отправляет **расшифрованные HTTP-запросы** в само HTTP‑приложение, работающее на том же сервере (в нашем случае это приложение **FastAPI**), получает **HTTP-ответ** от приложения, **шифрует его** с использованием подходящего **HTTPS‑сертификата** и отправляет клиенту по **HTTPS**. Такой сервер часто называют **<a href="https://en.wikipedia.org/wiki/TLS_termination_proxy" class="external-link" target="_blank">прокси‑сервером TLS-терминации</a>**.
Некоторые варианты, которые вы можете использовать как прокси‑сервер TLS-терминации:
* Traefik (умеет обновлять сертификаты)
* Caddy (умеет обновлять сертификаты)
* Nginx
* HAProxy
## Let's Encrypt { #lets-encrypt }
До появления Let's Encrypt эти **HTTPS-сертификаты** продавались доверенными третьими сторонами.
Процесс получения таких сертификатов был неудобным, требовал бумажной волокиты, а сами сертификаты были довольно дорогими.
Затем появился **<a href="https://letsencrypt.org/" class="external-link" target="_blank">Let's Encrypt</a>**.
Это проект Linux Foundation. Он предоставляет **HTTPS‑сертификаты бесплатно**, в автоматическом режиме. Эти сертификаты используют стандартные криптографические механизмы и имеют короткий срок действия (около 3 месяцев), поэтому **безопасность фактически выше** благодаря уменьшенному сроку жизни.
Домены безопасно проверяются, а сертификаты выдаются автоматически. Это также позволяет автоматизировать процесс их продления.
Идея — автоматизировать получение и продление сертификатов, чтобы у вас был **безопасный HTTPS, бесплатно и навсегда**.
## HTTPS для разработчиков { #https-for-developers }
Ниже приведён пример того, как может выглядеть HTTPS‑API, шаг за шагом, с акцентом на идеях, важных для разработчиков.
### Имя домена { #domain-name }
Чаще всего всё начинается с **приобретения** **имени домена**. Затем вы настраиваете его на DNS‑сервере (возможно, у того же облачного провайдера).
Скорее всего, вы получите облачный сервер (виртуальную машину) или что-то подобное, и у него будет <abbr title="Не изменяется">постоянный</abbr> **публичный IP-адрес**.
На DNS‑сервере(ах) вы настроите запись («`A record`» - запись типа A), указывающую, что **ваш домен** должен указывать на публичный **IP‑адрес вашего сервера**.
Обычно это делается один раз — при первоначальной настройке всего.
/// tip | Совет
Часть про доменное имя относится к этапам задолго до HTTPS, но так как всё зависит от домена и IP‑адреса, здесь стоит это упомянуть.
///
### DNS { #dns }
Теперь сфокусируемся на собственно частях, связанных с HTTPS.
Сначала браузер спросит у **DNS‑серверов**, какой **IP соответствует домену**, в нашем примере `someapp.example.com`.
DNS‑серверы ответят браузеру, какой **конкретный IP‑адрес** использовать. Это будет публичный IP‑адрес вашего сервера, который вы указали в настройках DNS.
<img src="/img/deployment/https/https01.drawio.svg">
### Начало TLS-рукопожатия { #tls-handshake-start }
Далее браузер будет общаться с этим IP‑адресом на **порту 443** (порт HTTPS).
Первая часть взаимодействия — установить соединение между клиентом и сервером и договориться о криптографических ключах и т.п.
<img src="/img/deployment/https/https02.drawio.svg">
Это взаимодействие клиента и сервера для установления TLS‑соединения называется **TLS‑рукопожатием**.
### TLS с расширением SNI { #tls-with-sni-extension }
На сервере **только один процесс** может слушать конкретный **порт** на конкретном **IP‑адресе**. Могут быть другие процессы, слушающие другие порты на том же IP‑адресе, но не более одного процесса на каждую комбинацию IP‑адреса и порта.
По умолчанию TLS (HTTPS) использует порт `443`. Значит, он нам и нужен.
Так как только один процесс может слушать этот порт, делать это будет **прокси‑сервер TLS-терминации**.
У прокси‑сервера TLS-терминации будет доступ к одному или нескольким **TLS‑сертификатам** (HTTPS‑сертификатам).
Используя **расширение SNI**, упомянутое выше, прокси‑сервер TLS-терминации определит, какой из доступных TLS (HTTPS)‑сертификатов нужно использовать для этого соединения, выбрав тот, который соответствует домену, ожидаемому клиентом.
В нашем случае это будет сертификат для `someapp.example.com`.
<img src="/img/deployment/https/https03.drawio.svg">
Клиент уже **доверяет** организации, выдавшей этот TLS‑сертификат (в нашем случае — Let's Encrypt, но об этом позже), поэтому может **проверить**, что сертификат действителен.
Затем, используя сертификат, клиент и прокси‑сервер TLS-терминации **договариваются о способе шифрования** остальной **TCP‑коммуникации**. На этом **TLS‑рукопожатие** завершено.
После этого у клиента и сервера есть **зашифрованное TCP‑соединение** — это и предоставляет TLS. И они могут использовать это соединение, чтобы начать собственно **HTTP‑обмен**.
Собственно, **HTTPS** — это обычный **HTTP** внутри **защищённого TLS‑соединения**, вместо чистого (незашифрованного) TCP‑соединения.
/// tip | Совет
Обратите внимание, что шифрование обмена происходит на **уровне TCP**, а не на уровне HTTP.
///
### HTTPS‑запрос { #https-request }
Теперь, когда у клиента и сервера (конкретно у браузера и прокси‑сервера TLS-терминации) есть **зашифрованное TCP‑соединение**, они могут начать **HTTP‑обмен**.
Клиент отправляет **HTTPS‑запрос**. Это обычный HTTP‑запрос через зашифрованное TLS‑соединение.
<img src="/img/deployment/https/https04.drawio.svg">
### Расшифровка запроса { #decrypt-the-request }
Прокси‑сервер TLS-терминации использует согласованное шифрование, чтобы **расшифровать запрос**, и передаёт **обычный (расшифрованный) HTTP‑запрос** процессу, запускающему приложение (например, процессу с Uvicorn, который запускает приложение FastAPI).
<img src="/img/deployment/https/https05.drawio.svg">
### HTTP‑ответ { #http-response }
Приложение обработает запрос и отправит **обычный (незашифрованный) HTTP‑ответ** прокси‑серверу TLS-терминации.
<img src="/img/deployment/https/https06.drawio.svg">
### HTTPS‑ответ { #https-response }
Затем прокси‑сервер TLS-терминации **зашифрует ответ** с использованием ранее согласованного способа шифрования (который начали использовать для сертификата для `someapp.example.com`) и отправит его обратно в браузер.
Далее браузер проверит, что ответ корректен и зашифрован правильным криптографическим ключом и т.п., затем **расшифрует ответ** и обработает его.
<img src="/img/deployment/https/https07.drawio.svg">
Клиент (браузер) узнает, что ответ пришёл от правильного сервера, потому что используется способ шифрования, о котором они договорились ранее с помощью **HTTPS‑сертификата**.
### Несколько приложений { #multiple-applications }
На одном и том же сервере (или серверах) могут работать **несколько приложений**, например другие программы с API или база данных.
Только один процесс может обрабатывать конкретную комбинацию IP и порта (в нашем примере — прокси‑сервер TLS-терминации), но остальные приложения/процессы тоже могут работать на сервере(ах), пока они не пытаются использовать ту же **комбинацию публичного IP и порта**.
<img src="/img/deployment/https/https08.drawio.svg">
Таким образом, прокси‑сервер TLS-терминации может обрабатывать HTTPS и сертификаты для **нескольких доменов** (для нескольких приложений), а затем передавать запросы нужному приложению в каждом случае.
### Продление сертификата { #certificate-renewal }
Со временем каждый сертификат **истечёт** (примерно через 3 месяца после получения).
Затем будет другая программа (иногда это отдельная программа, иногда — тот же прокси‑сервер TLS-терминации), которая свяжется с Let's Encrypt и продлит сертификат(ы).
<img src="/img/deployment/https/https.drawio.svg">
**TLS‑сертификаты** **связаны с именем домена**, а не с IP‑адресом.
Поэтому, чтобы продлить сертификаты, программа продления должна **доказать** удостоверяющему центру (Let's Encrypt), что она действительно **«владеет» и контролирует этот домен**.
Для этого, учитывая разные потребности приложений, есть несколько способов. Популярные из них:
* **Изменить некоторые DNS‑записи**.
* Для этого программа продления должна поддерживать API DNS‑провайдера, поэтому, в зависимости от используемого провайдера DNS, этот вариант может быть доступен или нет.
* **Запуститься как сервер** (как минимум на время получения сертификатов) на публичном IP‑адресе, связанном с доменом.
* Как сказано выше, только один процесс может слушать конкретный IP и порт.
* Это одна из причин, почему очень удобно, когда тот же прокси‑сервер TLS-терминации также занимается процессом продления сертификатов.
* В противном случае вам, возможно, придётся временно остановить прокси‑сервер TLS-терминации, запустить программу продления для получения сертификатов, затем настроить их в прокси‑сервере TLS-терминации и перезапустить его. Это не идеально, так как ваше приложение(я) будут недоступны, пока прокси‑сервер TLS-терминации остановлен.
Весь этот процесс продления, совмещённый с обслуживанием приложения, — одна из главных причин иметь **отдельную систему для работы с HTTPS** в виде прокси‑сервера TLS-терминации, вместо использования TLS‑сертификатов напрямую в сервере приложения (например, Uvicorn).
## Пересылаемые HTTP-заголовки прокси { #proxy-forwarded-headers }
Когда вы используете прокси для обработки HTTPS, ваш **сервер приложения** (например, Uvicorn через FastAPI CLI) ничего не знает о процессе HTTPS, он общается обычным HTTP с **прокси‑сервером TLS-терминации**.
Обычно этот **прокси** на лету добавляет некоторые HTTP‑заголовки перед тем, как переслать запрос на **сервер приложения**, чтобы тот знал, что запрос был **проксирован**.
/// note | Технические детали
Заголовки прокси:
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-For" class="external-link" target="_blank">X-Forwarded-For</a>
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Proto" class="external-link" target="_blank">X-Forwarded-Proto</a>
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Host" class="external-link" target="_blank">X-Forwarded-Host</a>
///
Тем не менее, так как **сервер приложения** не знает, что он находится за доверенным **прокси**, по умолчанию он не будет доверять этим заголовкам.
Но вы можете настроить **сервер приложения**, чтобы он доверял *пересылаемым* заголовкам, отправленным **прокси**. Если вы используете FastAPI CLI, вы можете использовать *опцию CLI* `--forwarded-allow-ips`, чтобы указать, с каких IP‑адресов следует доверять этим *пересылаемым* заголовкам.
Например, если **сервер приложения** получает запросы только от доверенного **прокси**, вы можете установить `--forwarded-allow-ips="*"`, чтобы доверять всем входящим IP, так как он всё равно будет получать запросы только с IP‑адреса, используемого **прокси**.
Таким образом, приложение сможет знать свой публичный URL, использует ли оно HTTPS, какой домен и т.п.
Это будет полезно, например, для корректной обработки редиректов.
/// tip | Совет
Подробнее об этом вы можете узнать в документации: [За прокси — Включить пересылаемые заголовки прокси](../advanced/behind-a-proxy.md#enable-proxy-forwarded-headers){.internal-link target=_blank}
///
## Резюме { #recap }
Наличие **HTTPS** очень важно и во многих случаях довольно **критично**. Большая часть усилий, которые вам, как разработчику, нужно приложить вокруг HTTPS, — это просто **понимание этих концепций** и того, как они работают.
Зная базовую информацию о **HTTPS для разработчиков**, вы сможете легко комбинировать и настраивать разные инструменты, чтобы управлять всем этим простым способом.
В некоторых из следующих глав я покажу вам несколько конкретных примеров настройки **HTTPS** для приложений **FastAPI**. 🔒
+21
View File
@@ -0,0 +1,21 @@
# Развёртывание { #deployment }
Развернуть приложение **FastAPI** довольно просто.
## Что означает развёртывание { #what-does-deployment-mean }
Термин **развёртывание** (приложения) означает выполнение необходимых шагов, чтобы сделать приложение **доступным для пользователей**.
Для **веб-API** это обычно означает размещение его на **удалённой машине** с **серверной программой**, обеспечивающей хорошую производительность, стабильность и т.д., чтобы ваши **пользователи** могли **получать доступ** к приложению эффективно и без перебоев или проблем.
Это отличается от этапов **разработки**, когда вы постоянно меняете код, ломаете его и исправляете, останавливаете и перезапускаете сервер разработки и т.д.
## Стратегии развёртывания { #deployment-strategies }
В зависимости от вашего конкретного случая, есть несколько способов сделать это.
Вы можете **развернуть сервер** самостоятельно, используя различные инструменты. Например, можно использовать **облачный сервис**, который выполнит часть работы за вас. Также возможны и другие варианты.
В этом блоке я покажу вам некоторые из основных концепций, которые вы, вероятно, должны иметь в виду при развертывании приложения **FastAPI** (хотя большинство из них применимо к любому другому типу веб-приложений).
В последующих разделах вы узнаете больше деталей и методов, необходимых для этого. ✨
+157
View File
@@ -0,0 +1,157 @@
# Запуск сервера вручную { #run-a-server-manually }
## Используйте команду `fastapi run` { #use-the-fastapi-run-command }
Коротко: используйте `fastapi run`, чтобы запустить ваше приложение FastAPI:
<div class="termy">
```console
$ <font color="#4E9A06">fastapi</font> run <u style="text-decoration-style:solid">main.py</u>
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting production 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://0.0.0.0: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://0.0.0.0:8000/docs</u></font>
Logs:
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>2306215</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.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000</u></font> <b>(</b>Press CTRL+C
to quit<b>)</b>
```
</div>
В большинстве случаев этого достаточно. 😎
Этой командой, например, можно запускать приложение **FastAPI** в контейнере, на сервере и т.д.
## ASGI‑серверы { #asgi-servers }
Давайте немного углубимся в детали.
FastAPI использует стандарт для построения Python‑веб‑фреймворков и серверов под названием <abbr title="Asynchronous Server Gateway Interface – Асинхронный шлюзовый интерфейс сервера">ASGI</abbr>. FastAPI — ASGI-веб‑фреймворк.
Главное, что вам нужно, чтобы запустить приложение **FastAPI** (или любое другое ASGI‑приложение) на удалённой серверной машине, — это программа ASGI‑сервера, такая как **Uvicorn**; именно он используется по умолчанию в команде `fastapi`.
Есть несколько альтернатив, например:
* <a href="https://www.uvicorn.dev/" class="external-link" target="_blank">Uvicorn</a>: высокопроизводительный ASGI‑сервер.
* <a href="https://hypercorn.readthedocs.io/" class="external-link" target="_blank">Hypercorn</a>: ASGI‑сервер, среди прочего совместимый с HTTP/2 и Trio.
* <a href="https://github.com/django/daphne" class="external-link" target="_blank">Daphne</a>: ASGI‑сервер, созданный для Django Channels.
* <a href="https://github.com/emmett-framework/granian" class="external-link" target="_blank">Granian</a>: HTTP‑сервер на Rust для Python‑приложений.
* <a href="https://unit.nginx.org/howto/fastapi/" class="external-link" target="_blank">NGINX Unit</a>: NGINX Unit — лёгкая и многофункциональная среда выполнения веб‑приложений.
## Сервер как машина и сервер как программа { #server-machine-and-server-program }
Есть небольшой нюанс в терминологии, о котором стоит помнить. 💡
Слово «сервер» обычно используют и для обозначения удалённого/облачного компьютера (физической или виртуальной машины), и для программы, работающей на этой машине (например, Uvicorn).
Имейте в виду, что слово «сервер» в целом может означать любое из этих двух.
Когда речь идёт об удалённой машине, её зачастую называют **сервер**, а также **машина**, **VM** (виртуальная машина), **нода**. Всё это — варианты названия удалённой машины, обычно под управлением Linux, на которой вы запускаете программы.
## Установка серверной программы { #install-the-server-program }
При установке FastAPI он поставляется с продакшн‑сервером Uvicorn, и вы можете запустить его командой `fastapi run`.
Но вы также можете установить ASGI‑сервер вручную.
Создайте [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активируйте его и затем установите серверное приложение.
Например, чтобы установить Uvicorn:
<div class="termy">
```console
$ pip install "uvicorn[standard]"
---> 100%
```
</div>
Аналогично устанавливаются и другие ASGI‑серверы.
/// tip | Совет
С добавлением `standard` Uvicorn установит и будет использовать ряд рекомендованных дополнительных зависимостей.
В их числе `uvloop` — высокопроизводительная замена `asyncio`, дающая серьёзный прирост производительности при параллельной работе.
Если вы устанавливаете FastAPI, например так: `pip install "fastapi[standard]"`, вы уже получаете и `uvicorn[standard]`.
///
## Запуск серверной программы { #run-the-server-program }
Если вы установили ASGI‑сервер вручную, обычно нужно передать строку импорта в специальном формате, чтобы он смог импортировать ваше приложение FastAPI:
<div class="termy">
```console
$ uvicorn main:app --host 0.0.0.0 --port 80
<span style="color: green;">INFO</span>: Uvicorn running on http://0.0.0.0:80 (Press CTRL+C to quit)
```
</div>
/// note | Примечание
Команда `uvicorn main:app` означает:
* `main`: файл `main.py` (Python‑«модуль»).
* `app`: объект, созданный в `main.py` строкой `app = FastAPI()`.
Эквивалентно:
```Python
from main import app
```
///
У каждого альтернативного ASGI‑сервера будет похожая команда; подробнее см. в их документации.
/// warning | Предупреждение
Uvicorn и другие серверы поддерживают опцию `--reload`, полезную в период разработки.
Опция `--reload` потребляет значительно больше ресурсов, менее стабильна и т.п.
Она сильно помогает во время **разработки**, но в **продакшн** её использовать **не следует**.
///
## Концепции развёртывания { #deployment-concepts }
В этих примерах серверная программа (например, Uvicorn) запускает **один процесс**, слушающий все IP‑адреса (`0.0.0.0`) на заранее заданном порту (например, `80`).
Это базовая идея. Но, вероятно, вам понадобится позаботиться и о некоторых дополнительных вещах, например:
* Безопасность — HTTPS
* Запуск при старте системы
* Перезапуски
* Репликация (количество запущенных процессов)
* Память
* Предварительные шаги перед запуском
В следующих главах я расскажу подробнее про каждую из этих концепций, о том, как о них думать, и приведу конкретные примеры со стратегиями, как с ними работать. 🚀
+139
View File
@@ -0,0 +1,139 @@
# Серверные воркеры — Uvicorn с воркерами { #server-workers-uvicorn-with-workers }
Давайте снова вспомним те концепции деплоя, о которых говорили ранее:
* Безопасность — HTTPS
* Запуск при старте
* Перезапуски
* **Репликация (количество запущенных процессов)**
* Память
* Предварительные шаги перед запуском
До этого момента, следуя руководствам в документации, вы, вероятно, запускали **серверную программу**, например с помощью команды `fastapi`, которая запускает Uvicorn в **одном процессе**.
При деплое приложения вам, скорее всего, захочется использовать **репликацию процессов**, чтобы задействовать **несколько ядер** и иметь возможность обрабатывать больше запросов.
Как вы видели в предыдущей главе о [Концепциях деплоя](concepts.md){.internal-link target=_blank}, существует несколько стратегий.
Здесь я покажу, как использовать **Uvicorn** с **воркер-процессами** через команду `fastapi` или напрямую через команду `uvicorn`.
/// info | Информация
Если вы используете контейнеры, например Docker или Kubernetes, я расскажу об этом подробнее в следующей главе: [FastAPI в контейнерах — Docker](docker.md){.internal-link target=_blank}.
В частности, при запуске в **Kubernetes** вам, скорее всего, **не** понадобится использовать воркеры — вместо этого запускайте **один процесс Uvicorn на контейнер**, но об этом подробнее далее в той главе.
///
## Несколько воркеров { #multiple-workers }
Можно запустить несколько воркеров с помощью опции командной строки `--workers`:
//// tab | `fastapi`
Если вы используете команду `fastapi`:
<div class="termy">
```console
$ <font color="#4E9A06">fastapi</font> run --workers 4 <u style="text-decoration-style:solid">main.py</u>
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting production 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://0.0.0.0: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://0.0.0.0:8000/docs</u></font>
Logs:
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000</u></font> <b>(</b>Press CTRL+C to
quit<b>)</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started parent process <b>[</b><font color="#34E2E2"><b>27365</b></font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>27368</b></font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>27369</b></font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>27370</b></font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>27367</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> Waiting for application startup.
<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> Waiting for application startup.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
```
</div>
////
//// tab | `uvicorn`
Если вы предпочитаете использовать команду `uvicorn` напрямую:
<div class="termy">
```console
$ uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4
<font color="#A6E22E">INFO</font>: Uvicorn running on <b>http://0.0.0.0:8080</b> (Press CTRL+C to quit)
<font color="#A6E22E">INFO</font>: Started parent process [<font color="#A1EFE4"><b>27365</b></font>]
<font color="#A6E22E">INFO</font>: Started server process [<font color="#A1EFE4">27368</font>]
<font color="#A6E22E">INFO</font>: Waiting for application startup.
<font color="#A6E22E">INFO</font>: Application startup complete.
<font color="#A6E22E">INFO</font>: Started server process [<font color="#A1EFE4">27369</font>]
<font color="#A6E22E">INFO</font>: Waiting for application startup.
<font color="#A6E22E">INFO</font>: Application startup complete.
<font color="#A6E22E">INFO</font>: Started server process [<font color="#A1EFE4">27370</font>]
<font color="#A6E22E">INFO</font>: Waiting for application startup.
<font color="#A6E22E">INFO</font>: Application startup complete.
<font color="#A6E22E">INFO</font>: Started server process [<font color="#A1EFE4">27367</font>]
<font color="#A6E22E">INFO</font>: Waiting for application startup.
<font color="#A6E22E">INFO</font>: Application startup complete.
```
</div>
////
Единственная новая опция здесь — `--workers`, она говорит Uvicorn запустить 4 воркер-процесса.
Также видно, что выводится **PID** каждого процесса: `27365` — для родительского процесса (это **менеджер процессов**) и по одному для каждого воркер-процесса: `27368`, `27369`, `27370` и `27367`.
## Концепции деплоя { #deployment-concepts }
Здесь вы увидели, как использовать несколько **воркеров**, чтобы **распараллелить** выполнение приложения, задействовать **несколько ядер** CPU и обслуживать **больше запросов**.
Из списка концепций деплоя выше использование воркеров в основном помогает с **репликацией**, и немного — с **перезапусками**, но об остальных по-прежнему нужно позаботиться:
* **Безопасность — HTTPS**
* **Запуск при старте**
* ***Перезапуски***
* Репликация (количество запущенных процессов)
* **Память**
* **Предварительные шаги перед запуском**
## Контейнеры и Docker { #containers-and-docker }
В следующей главе о [FastAPI в контейнерах — Docker](docker.md){.internal-link target=_blank} я объясню стратегии, которые можно использовать для решения остальных **концепций деплоя**.
Я покажу, как **собрать свой образ с нуля**, чтобы запускать один процесс Uvicorn. Это простой подход и, вероятно, именно то, что вам нужно при использовании распределённой системы управления контейнерами, такой как **Kubernetes**.
## Резюме { #recap }
Вы можете использовать несколько воркер-процессов с опцией командной строки `--workers` в командах `fastapi` или `uvicorn`, чтобы задействовать **многоядерные CPU**, запуская **несколько процессов параллельно**.
Вы можете использовать эти инструменты и идеи, если настраиваете **собственную систему деплоя** и самостоятельно закрываете остальные концепции деплоя.
Перейдите к следующей главе, чтобы узнать о **FastAPI** в контейнерах (например, Docker и Kubernetes). Вы увидите, что эти инструменты тоже предлагают простые способы решить другие **концепции деплоя**. ✨
+93
View File
@@ -0,0 +1,93 @@
# О версиях FastAPI { #about-fastapi-versions }
**FastAPI** уже используется в продакшене во многих приложениях и системах. Покрытие тестами поддерживается на уровне 100%. Но его разработка всё ещё движется быстрыми темпами.
Часто добавляются новые функции, регулярно исправляются баги, код продолжает постоянно совершенствоваться.
По указанным причинам текущие версии до сих пор `0.x.x`. Это говорит о том, что каждая версия может содержать обратно несовместимые изменения, следуя <a href="https://semver.org/" class="external-link" target="_blank">Семантическому версионированию</a>.
Уже сейчас вы можете создавать приложения в продакшене, используя **FastAPI** (и скорее всего так и делаете), главное убедиться в том, что вы используете версию, которая корректно работает с вашим кодом.
## Закрепите вашу версию `fastapi` { #pin-your-fastapi-version }
Первым делом вам следует "закрепить" конкретную последнюю используемую версию **FastAPI**, которая корректно работает с вашим приложением.
Например, в своём приложении вы используете версию `0.112.0`.
Если вы используете файл `requirements.txt`, вы можете указать версию следующим способом:
```txt
fastapi[standard]==0.112.0
```
это означает, что вы будете использовать именно версию `0.112.0`.
Или вы можете закрепить версию следующим способом:
```txt
fastapi[standard]>=0.112.0,<0.113.0
```
это значит, что вы используете версии `0.112.0` или выше, но меньше чем `0.113.0`. Например, версия `0.112.2` всё ещё будет подходить.
Если вы используете любой другой инструмент для управления установками/зависимостями, например `uv`, Poetry, Pipenv или др., у них у всех имеется способ определения специфической версии для ваших пакетов.
## Доступные версии { #available-versions }
Вы можете посмотреть доступные версии (например, проверить последнюю на данный момент) в [Примечаниях к выпуску](../release-notes.md){.internal-link target=_blank}.
## О версиях { #about-versions }
Следуя соглашению о Семантическом Версионировании, любые версии ниже `1.0.0` потенциально могут добавить обратно несовместимые изменения.
FastAPI следует соглашению в том, что любые изменения "ПАТЧ"-версии предназначены для исправления багов и внесения обратно совместимых изменений.
/// tip | Подсказка
"ПАТЧ" — это последнее число. Например, в `0.2.3`, ПАТЧ-версия — это `3`.
///
Итак, вы можете закрепить версию следующим образом:
```txt
fastapi>=0.45.0,<0.46.0
```
Обратно несовместимые изменения и новые функции добавляются в "МИНОРНЫЕ" версии.
/// tip | Подсказка
"МИНОРНАЯ" версия — это число в середине. Например, в `0.2.3` МИНОРНАЯ версия — это `2`.
///
## Обновление версий FastAPI { #upgrading-the-fastapi-versions }
Вам следует добавить тесты для вашего приложения.
С помощью **FastAPI** это очень просто (благодаря Starlette), см. документацию: [Тестирование](../tutorial/testing.md){.internal-link target=_blank}
После создания тестов вы можете обновить свою версию **FastAPI** до более новой. После этого следует убедиться, что ваш код работает корректно, запустив тесты.
Если всё работает корректно, или после внесения необходимых изменений все ваши тесты проходят, только тогда вы можете закрепить вашу новую версию `fastapi`.
## О Starlette { #about-starlette }
Не следует закреплять версию `starlette`.
Разные версии **FastAPI** будут использовать более новые версии Starlette.
Так что решение об используемой версии Starlette, вы можете оставить **FastAPI**.
## О Pydantic { #about-pydantic }
Pydantic включает свои собственные тесты для **FastAPI**, так что новые версии Pydantic (выше `1.0.0`) всегда совместимы с FastAPI.
Вы можете закрепить любую версию Pydantic, которая вам подходит, выше `1.0.0`.
Например:
```txt
pydantic>=2.7.0,<3.0.0
```
+298
View File
@@ -0,0 +1,298 @@
# Переменные окружения { #environment-variables }
/// tip | Совет
Если вы уже знаете, что такое «переменные окружения» и как их использовать, можете пропустить это.
///
Переменная окружения (также известная как «**env var**») - это переменная, которая живет **вне** кода Python, в **операционной системе**, и может быть прочитана вашим кодом Python (или другими программами).
Переменные окружения могут быть полезны для работы с **настройками** приложений, как часть **установки** Python и т.д.
## Создание и использование переменных окружения { #create-and-use-env-vars }
Можно **создавать** и использовать переменные окружения в **оболочке (терминале)**, не прибегая к помощи Python:
//// tab | Linux, macOS, Windows Bash
<div class="termy">
```console
// Вы можете создать переменную окружения MY_NAME с помощью
$ export MY_NAME="Wade Wilson"
// Затем её можно использовать в других программах, например
$ echo "Hello $MY_NAME"
Hello Wade Wilson
```
</div>
////
//// tab | Windows PowerShell
<div class="termy">
```console
// Создайте переменную окружения MY_NAME
$ $Env:MY_NAME = "Wade Wilson"
// Используйте её с другими программами, например
$ echo "Hello $Env:MY_NAME"
Hello Wade Wilson
```
</div>
////
## Чтение переменных окружения в python { #read-env-vars-in-python }
Так же существует возможность создания переменных окружения **вне** Python, в терминале (или любым другим способом), а затем **чтения их в Python**.
Например, у вас есть файл `main.py`:
```Python hl_lines="3"
import os
name = os.getenv("MY_NAME", "World")
print(f"Hello {name} from Python")
```
/// tip | Совет
Второй аргумент <a href="https://docs.python.org/3.8/library/os.html#os.getenv" class="external-link" target="_blank">`os.getenv()`</a> - это возвращаемое по умолчанию значение.
Если значение не указано, то по умолчанию оно равно `None`. В данном случае мы указываем `«World»` в качестве значения по умолчанию.
///
Затем можно запустить эту программу на Python:
//// tab | Linux, macOS, Windows Bash
<div class="termy">
```console
// Здесь мы еще не устанавливаем переменную окружения
$ python main.py
// Поскольку мы не задали переменную окружения, мы получим значение по умолчанию
Hello World from Python
// Но если мы сначала создадим переменную окружения
$ export MY_NAME="Wade Wilson"
// А затем снова запустим программу
$ python main.py
// Теперь она прочитает переменную окружения
Hello Wade Wilson from Python
```
</div>
////
//// tab | Windows PowerShell
<div class="termy">
```console
// Здесь мы еще не устанавливаем переменную окружения
$ python main.py
// Поскольку мы не задали переменную окружения, мы получим значение по умолчанию
Hello World from Python
// Но если мы сначала создадим переменную окружения
$ $Env:MY_NAME = "Wade Wilson"
// А затем снова запустим программу
$ python main.py
// Теперь она может прочитать переменную окружения
Hello Wade Wilson from Python
```
</div>
////
Поскольку переменные окружения могут быть установлены вне кода, но могут быть прочитаны кодом, и их не нужно хранить (фиксировать в `git`) вместе с остальными файлами, их принято использовать для конфигураций или **настроек**.
Вы также можете создать переменную окружения только для **конкретного вызова программы**, которая будет доступна только для этой программы и только на время ее выполнения.
Для этого создайте её непосредственно перед самой программой, в той же строке:
<div class="termy">
```console
// Создайте переменную окружения MY_NAME в строке для этого вызова программы
$ MY_NAME="Wade Wilson" python main.py
// Теперь она может прочитать переменную окружения
Hello Wade Wilson from Python
// После этого переменная окружения больше не существует
$ python main.py
Hello World from Python
```
</div>
/// tip | Совет
Подробнее об этом можно прочитать на сайте <a href="https://12factor.net/config" class="external-link" target="_blank">The Twelve-Factor App: Config</a>.
///
## Типизация и Валидация { #types-and-validation }
Эти переменные окружения могут работать только с **текстовыми строками**, поскольку они являются внешними по отношению к Python и должны быть совместимы с другими программами и остальной системой (и даже с различными операционными системами, такими как Linux, Windows, macOS).
Это означает, что **любое значение**, считанное в Python из переменной окружения, **будет `str`**, и любое преобразование к другому типу или любая проверка должны быть выполнены в коде.
Подробнее об использовании переменных окружения для работы с **настройками приложения** вы узнаете в [Расширенное руководство пользователя - Настройки и переменные среды](./advanced/settings.md){.internal-link target=_blank}.
## Переменная окружения `PATH` { #path-environment-variable }
Существует **специальная** переменная окружения **`PATH`**, которая используется операционными системами (Linux, macOS, Windows) для поиска программ для запуска.
Значение переменной `PATH` - это длинная строка, состоящая из каталогов, разделенных двоеточием `:` в Linux и macOS, и точкой с запятой `;` в Windows.
Например, переменная окружения `PATH` может выглядеть следующим образом:
//// tab | Linux, macOS
```plaintext
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
```
Это означает, что система должна искать программы в каталогах:
* `/usr/local/bin`
* `/usr/bin`
* `/bin`
* `/usr/sbin`
* `/sbin`
////
//// tab | Windows
```plaintext
C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32
```
Это означает, что система должна искать программы в каталогах:
* `C:\Program Files\Python312\Scripts`
* `C:\Program Files\Python312`
* `C:\Windows\System32`
////
Когда вы вводите **команду** в терминале, операционная система **ищет** программу в **каждой из тех директорий**, которые перечислены в переменной окружения `PATH`.
Например, когда вы вводите `python` в терминале, операционная система ищет программу под названием `python` в **первой директории** в этом списке.
Если она ее находит, то **использует ее**. В противном случае она продолжает искать в **других каталогах**.
### Установка Python и обновление `PATH` { #installing-python-and-updating-the-path }
При установке Python вас могут спросить, нужно ли обновить переменную окружения `PATH`.
//// tab | Linux, macOS
Допустим, вы устанавливаете Python, и он оказывается в каталоге `/opt/custompython/bin`.
Если вы скажете «да», чтобы обновить переменную окружения `PATH`, то программа установки добавит `/opt/custompython/bin` в переменную окружения `PATH`.
Это может выглядеть следующим образом:
```plaintext
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/custompython/bin
```
Таким образом, когда вы набираете `python` в терминале, система найдет программу Python в `/opt/custompython/bin` (последний каталог) и использует ее.
////
//// tab | Windows
Допустим, вы устанавливаете Python, и он оказывается в каталоге `C:\opt\custompython\bin`.
Если вы согласитесь обновить переменную окружения `PATH`, то программа установки добавит `C:\opt\custompython\bin` в переменную окружения `PATH`.
```plaintext
C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32;C:\opt\custompython\bin
```
Таким образом, когда вы набираете `python` в терминале, система найдет программу Python в `C:\opt\custompython\bin` (последний каталог) и использует ее.
////
Итак, если вы напечатаете:
<div class="termy">
```console
$ python
```
</div>
//// tab | Linux, macOS
Система **найдет** программу `python` в `/opt/custompython/bin` и запустит ее.
Это примерно эквивалентно набору текста:
<div class="termy">
```console
$ /opt/custompython/bin/python
```
</div>
////
//// tab | Windows
Система **найдет** программу `python` в каталоге `C:\opt\custompython\bin\python` и запустит ее.
Это примерно эквивалентно набору текста:
<div class="termy">
```console
$ C:\opt\custompython\bin\python
```
</div>
////
Эта информация будет полезна при изучении [Виртуальных окружений](virtual-environments.md){.internal-link target=_blank}.
## Вывод { #conclusion }
Благодаря этому вы должны иметь базовое представление о том, что такое **переменные окружения** и как использовать их в Python.
Подробнее о них вы также можете прочитать в <a href="https://en.wikipedia.org/wiki/Environment_variable" class="external-link" target="_blank">статье о переменных окружения на википедии</a>.
Во многих случаях не всегда очевидно, как переменные окружения могут быть полезны и применимы. Но они постоянно появляются в различных сценариях разработки, поэтому знать о них полезно.
Например, эта информация понадобится вам в следующем разделе, посвященном [Виртуальным окружениям](virtual-environments.md).
+75
View File
@@ -0,0 +1,75 @@
# FastAPI CLI { #fastapi-cli }
**FastAPI CLI** это программа командной строки, которую вы можете использовать для запуска вашего FastAPI приложения, для управления FastAPI-проектом, а также для многих других вещей.
`fastapi-cli` устанавливается вместе со стандартным пакетом FastAPI (при запуске команды `pip install "fastapi[standard]"`). Данный пакет предоставляет доступ к программе `fastapi` через терминал.
Чтобы запустить приложение FastAPI в режиме разработки, вы можете использовать команду `fastapi dev`:
<div class="termy">
```console
$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:solid">main.py</u>
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting development server 🚀
Searching for package file structure from directories with
<font color="#3465A4">__init__.py</font> files
Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
<span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> 🐍 main.py
<span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with the
following code:
<u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u>
<span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000/docs</u></font>
<span style="background-color:#007166"><font color="#D3D7CF"> tip </font></span> Running in development mode, for production use:
<b>fastapi run</b>
Logs:
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Will watch for changes in these directories:
<b>[</b><font color="#4E9A06">&apos;/home/user/code/awesomeapp&apos;</font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font> <b>(</b>Press CTRL+C to
quit<b>)</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started reloader process <b>[</b><font color="#34E2E2"><b>383138</b></font><b>]</b> using WatchFiles
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>383153</b></font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
```
</div>
Приложение командной строки `fastapi` это и есть **FastAPI CLI**.
FastAPI CLI берет путь к вашей Python-программе (напр. `main.py`) и автоматически находит объект `FastAPI` (обычно это `app`), затем определяет правильный процесс импорта и запускает сервер приложения.
Для работы в режиме продакшн вместо `fastapi dev` нужно использовать `fastapi run`. 🚀
Внутри **FastAPI CLI** используется <a href="https://www.uvicorn.dev" class="external-link" target="_blank">Uvicorn</a>, высокопроизводительный, готовый к работе в продакшне ASGI-сервер. 😎
## `fastapi dev` { #fastapi-dev }
Вызов `fastapi dev` запускает режим разработки.
По умолчанию включена авто-перезагрузка (**auto-reload**), благодаря этому при изменении кода происходит перезагрузка сервера приложения. Эта установка требует значительных ресурсов и делает систему менее стабильной. Используйте её только при разработке. Приложение слушает входящие подключения на IP `127.0.0.1`. Это IP адрес вашей машины, предназначенный для внутренних коммуникаций (`localhost`).
## `fastapi run` { #fastapi-run }
Вызов `fastapi run` по умолчанию запускает FastAPI в режиме продакшн.
По умолчанию авто-перезагрузка (**auto-reload**) отключена. Приложение слушает входящие подключения на IP `0.0.0.0`, т.е. на всех доступных адресах компьютера. Таким образом, приложение будет находиться в публичном доступе для любого, кто может подсоединиться к вашей машине. Продуктовые приложения запускаются именно так, например, с помощью контейнеров.
В большинстве случаев вы будете (и должны) использовать прокси-сервер ("termination proxy"), который будет поддерживать HTTPS поверх вашего приложения. Всё будет зависеть от того, как вы развертываете приложение: за вас это либо сделает ваш провайдер, либо вам придется сделать настройки самостоятельно.
/// tip | Подсказка
Вы можете больше узнать об этом в [документации по развертыванию](deployment/index.md){.internal-link target=_blank}.
///
+201
View File
@@ -0,0 +1,201 @@
# Возможности { #features }
## Возможности FastAPI { #fastapi-features }
**FastAPI** предлагает вам следующее:
### Основано на открытых стандартах { #based-on-open-standards }
* <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank"><strong>OpenAPI</strong></a> для создания API, включая объявления <abbr title="также известные как HTTP-методы, например: POST, GET, PUT, DELETE">операций</abbr> <abbr title="также известен как: эндпоинты, маршруты)">пути</abbr>, параметров, тел запросов, безопасности и т. д.
* Автоматическая документация моделей данных с помощью <a href="https://json-schema.org/" class="external-link" target="_blank"><strong>JSON Schema</strong></a> (так как сама спецификация OpenAPI основана на JSON Schema).
* Разработан вокруг этих стандартов, после тщательного их изучения. Это не дополнительная надстройка поверх.
* Это также позволяет использовать автоматическую **генерацию клиентского кода** на многих языках.
### Автоматическая документация { #automatic-docs }
Интерактивная документация для API и исследовательские веб-интерфейсы. Поскольку фреймворк основан на OpenAPI, существует несколько вариантов документирования, 2 из них включены по умолчанию.
* <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank"><strong>Swagger UI</strong></a>, с интерактивным исследованием, вызовом и тестированием вашего API прямо из браузера.
![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png)
* Альтернативная документация API в <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank"><strong>ReDoc</strong></a>.
![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png)
### Только современный Python { #just-modern-python }
Все основано на стандартных **аннотациях типов Python** (благодаря Pydantic). Не нужно изучать новый синтаксис. Только стандартный современный Python.
Если вам нужно освежить знания о типах в Python (даже если вы не используете FastAPI), выделите 2 минуты и просмотрите краткое руководство: [Типы Python](python-types.md){.internal-link target=_blank}.
Вы пишете стандартный Python с типами:
```Python
from datetime import date
from pydantic import BaseModel
# Объявляем параметр как `str`
# и получаем поддержку редактора кода внутри функции
def main(user_id: str):
return user_id
# Модель Pydantic
class User(BaseModel):
id: int
name: str
joined: date
```
Это можно использовать так:
```Python
my_user: User = User(id=3, name="John Doe", joined="2018-07-19")
second_user_data = {
"id": 4,
"name": "Mary",
"joined": "2018-11-30",
}
my_second_user: User = User(**second_user_data)
```
/// info | Информация
`**second_user_data` означает:
Передать ключи и значения словаря `second_user_data` в качестве аргументов "ключ-значение", эквивалентно: `User(id=4, name="Mary", joined="2018-11-30")`
///
### Поддержка редакторов (IDE) { #editor-support }
Весь фреймворк был продуман так, чтобы быть простым и интуитивно понятным в использовании, все решения были проверены на множестве редакторов еще до начала разработки, чтобы обеспечить наилучшие условия при написании кода.
В опросах Python‑разработчиков видно, <a href="https://www.jetbrains.com/research/python-developers-survey-2017/#tools-and-features" class="external-link" target="_blank">что одной из самых часто используемых функций является «автозавершение»</a>.
Вся структура **FastAPI** основана на удовлетворении этой возможности. Автозавершение работает везде.
Вам редко нужно будет возвращаться к документации.
Вот как ваш редактор может вам помочь:
* в <a href="https://code.visualstudio.com/" class="external-link" target="_blank">Visual Studio Code</a>:
![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png)
* в <a href="https://www.jetbrains.com/pycharm/" class="external-link" target="_blank">PyCharm</a>:
![editor support](https://fastapi.tiangolo.com/img/pycharm-completion.png)
Вы будете получать автозавершение кода даже там, где вы считали это невозможным раньше. Как пример, ключ `price` внутри тела JSON (который может быть вложенным), приходящего в запросе.
Больше никаких неправильных имён ключей, метания по документации или прокручивания кода вверх и вниз в попытках узнать — использовали вы ранее `username` или `user_name`.
### Краткость { #short }
FastAPI имеет продуманные значения **по умолчанию** для всего, с опциональными настройками везде. Все параметры могут быть тонко подстроены так, чтобы делать то, что вам нужно, и определять необходимый вам API.
Но по умолчанию всё **«просто работает»**.
### Проверка значений { #validation }
* Проверка значений для большинства (или всех?) **типов данных** Python, включая:
* Объекты JSON (`dict`).
* Массив JSON (`list`) с определёнными типами элементов.
* Строковые (`str`) поля с ограничением минимальной и максимальной длины.
* Числа (`int`, `float`) с минимальными и максимальными значениями и т. п.
* Проверка для более экзотических типов, таких как:
* URL.
* Email.
* UUID.
* ...и другие.
Все проверки обрабатываются хорошо зарекомендовавшим себя и надёжным **Pydantic**.
### Безопасность и аутентификация { #security-and-authentication }
Встроенные функции безопасности и аутентификации. Без каких‑либо компромиссов с базами данных или моделями данных.
Все схемы безопасности, определённые в OpenAPI, включая:
* HTTP Basic.
* **OAuth2** (также с **токенами JWT**). Ознакомьтесь с руководством [OAuth2 с JWT](tutorial/security/oauth2-jwt.md){.internal-link target=_blank}.
* Ключи API в:
* Заголовках.
* Параметрах запросов.
* Cookies и т.п.
Вдобавок все функции безопасности от Starlette (включая **сессионные cookies**).
Все инструменты и компоненты спроектированы для многократного использования и легко интегрируются с вашими системами, хранилищами данных, реляционными и NoSQL базами данных и т. д.
### Внедрение зависимостей { #dependency-injection }
FastAPI включает в себя чрезвычайно простую в использовании, но чрезвычайно мощную систему <abbr title='известную как: "components", "resources", "services", "providers"'><strong>Внедрения зависимостей</strong></abbr>.
* Даже зависимости могут иметь зависимости, создавая иерархию или **«граф» зависимостей**.
* Всё **автоматически обрабатывается** фреймворком.
* Все зависимости могут запрашивать данные из запросов и **дополнять операции пути** ограничениями и автоматической документацией.
* **Автоматическая проверка** даже для параметров *операций пути*, определённых в зависимостях.
* Поддержка сложных систем аутентификации пользователей, **соединений с базами данных** и т. д.
* **Никаких компромиссов** с базами данных, интерфейсами и т. д. Но при этом — лёгкая интеграция со всеми ними.
### Нет ограничений на "Плагины" { #unlimited-plug-ins }
Или, другими словами, нет необходимости в них — просто импортируйте и используйте нужный вам код.
Любая интеграция разработана настолько простой в использовании (с зависимостями), что вы можете создать «плагин» для своего приложения в пару строк кода, используя ту же структуру и синтаксис, что и для ваших *операций пути*.
### Проверен { #tested }
* 100% <abbr title="Количество автоматически проверяемого кода">покрытие тестами</abbr>.
* 100% <abbr title="Аннотации типов Python, благодаря которым ваш редактор и внешние инструменты могут обеспечить вам лучшую поддержку">аннотирование типов</abbr> в кодовой базе.
* Используется в продакшн‑приложениях.
## Возможности Starlette { #starlette-features }
**FastAPI** основан на <a href="https://www.starlette.dev/" class="external-link" target="_blank"><strong>Starlette</strong></a> и полностью совместим с ним. Так что любой дополнительный код Starlette, который у вас есть, также будет работать.
На самом деле, `FastAPI` — это подкласс `Starlette`. Таким образом, если вы уже знаете или используете Starlette, большая часть функционала будет работать так же.
С **FastAPI** вы получаете все возможности **Starlette** (так как FastAPI — это всего лишь Starlette на стероидах):
* Серьёзно впечатляющая производительность. Это <a href="https://github.com/encode/starlette#performance" class="external-link" target="_blank">один из самых быстрых фреймворков на Python, наравне с **NodeJS** и **Go**</a>.
* Поддержка **WebSocket**.
* Фоновые задачи в том же процессе.
* События запуска и выключения.
* Тестовый клиент построен на HTTPX.
* **CORS**, GZip, статические файлы, потоковые ответы.
* Поддержка **сессий и cookie**.
* 100% покрытие тестами.
* 100% аннотирование типов в кодовой базе.
## Возможности Pydantic { #pydantic-features }
**FastAPI** полностью совместим с (и основан на) <a href="https://docs.pydantic.dev/" class="external-link" target="_blank"><strong>Pydantic</strong></a>. Поэтому любой дополнительный код Pydantic, который у вас есть, также будет работать.
Включая внешние библиотеки, также основанные на Pydantic, такие как <abbr title="Object-Relational Mapper">ORM</abbr>’ы, <abbr title="Object-Document Mapper">ODM</abbr>’ы для баз данных.
Это также означает, что во многих случаях вы можете передавать тот же объект, который получили из запроса, **непосредственно в базу данных**, так как всё проверяется автоматически.
И наоборот, во многих случаях вы можете просто передать объект, полученный из базы данных, **непосредственно клиенту**.
С **FastAPI** вы получаете все возможности **Pydantic** (так как FastAPI основан на Pydantic для обработки данных):
* **Никакой нервотрёпки**:
* Не нужно изучать новые схемы в микроязыках.
* Если вы знаете типы в Python, вы знаете, как использовать Pydantic.
* Прекрасно сочетается с вашим **<abbr title="Integrated Development Environment - Интегрированная среда разработки: попросту «редактора кода»">IDE</abbr>/<abbr title="Программа, проверяющая ошибки в коде">linter</abbr>/мозгом**:
* Потому что структуры данных pydantic — это всего лишь экземпляры классов, определённых вами; автозавершение, проверка кода, mypy и ваша интуиция — всё будет работать с вашими валидированными данными.
* Валидация **сложных структур**:
* Использование иерархических моделей Pydantic; `List`, `Dict` и т. п. из модуля `typing` (входит в стандартную библиотеку Python).
* Валидаторы позволяют чётко и легко определять, проверять и документировать сложные схемы данных в виде JSON Schema.
* У вас могут быть глубоко **вложенные объекты JSON**, и все они будут проверены и аннотированы.
* **Расширяемость**:
* Pydantic позволяет определять пользовательские типы данных или расширять проверку методами модели с помощью декораторов валидаторов.
* 100% покрытие тестами.
+255
View File
@@ -0,0 +1,255 @@
# Помочь FastAPI - Получить помощь { #help-fastapi-get-help }
Нравится ли Вам **FastAPI**?
Хотели бы Вы помочь FastAPI, другим пользователям и автору?
Или Вы хотите получить помощь по **FastAPI**?
Есть несколько очень простых способов помочь (иногда достаточно всего лишь одного-двух кликов).
И также есть несколько способов получить помощь.
## Подписаться на новостную рассылку { #subscribe-to-the-newsletter }
Вы можете подписаться на редкую [новостную рассылку **FastAPI и его друзья**](newsletter.md){.internal-link target=_blank} и быть в курсе о:
* Новостях о FastAPI и его друзьях 🚀
* Руководствах 📝
* Возможностях ✨
* Ломающих изменениях 🚨
* Подсказках и хитростях ✅
## Подписаться на FastAPI в X (Twitter) { #follow-fastapi-on-x-twitter }
<a href="https://x.com/fastapi" class="external-link" target="_blank">Подписаться на @fastapi в **X (Twitter)**</a> для получения наисвежайших новостей о **FastAPI**. 🐦
## Добавить **FastAPI** звезду на GitHub { #star-fastapi-in-github }
Вы можете добавить FastAPI "звезду" на GitHub (кликнув на кнопку звезды в правом верхнем углу): <a href="https://github.com/fastapi/fastapi" class="external-link" target="_blank">https://github.com/fastapi/fastapi</a>. ⭐️
Чем больше звёзд, тем легче другим пользователям найти проект и увидеть, что он уже оказался полезным для многих.
## Отслеживать свежие выпуски в репозитории на GitHub { #watch-the-github-repository-for-releases }
Вы можете "отслеживать" FastAPI на GitHub (кликнув по кнопке "watch" наверху справа): <a href="https://github.com/fastapi/fastapi" class="external-link" target="_blank">https://github.com/fastapi/fastapi</a>. 👀
Там же Вы можете выбрать "Releases only".
С такой настройкой Вы будете получать уведомления на вашу электронную почту каждый раз, когда появится новый релиз (новая версия) **FastAPI** с исправлениями ошибок и новыми возможностями.
## Связаться с автором { #connect-with-the-author }
Можно связаться со <a href="https://tiangolo.com" class="external-link" target="_blank">мной (Sebastián Ramírez / `tiangolo`)</a>, автором.
Вы можете:
* <a href="https://github.com/tiangolo" class="external-link" target="_blank">Подписаться на меня на **GitHub**</a>.
* Посмотреть другие мои проекты с открытым кодом, которые могут быть полезны Вам.
* Подписаться, чтобы видеть, когда я создаю новый проект с открытым кодом.
* <a href="https://x.com/tiangolo" class="external-link" target="_blank">Подписаться на меня в **X (Twitter)**</a> или в <a href="https://fosstodon.org/@tiangolo" class="external-link" target="_blank">Mastodon</a>.
* Поделиться со мной, как Вы используете FastAPI (я обожаю это читать).
* Узнавать, когда я делаю объявления или выпускаю новые инструменты.
* Вы также можете <a href="https://x.com/fastapi" class="external-link" target="_blank">подписаться на @fastapi в X (Twitter)</a> (это отдельный аккаунт).
* <a href="https://www.linkedin.com/in/tiangolo/" class="external-link" target="_blank">Подписаться на меня в **LinkedIn**</a>.
* Узнавать, когда я делаю объявления или выпускаю новые инструменты (хотя чаще я использую X (Twitter) 🤷‍♂).
* Читать, что я пишу (или подписаться на меня) на <a href="https://dev.to/tiangolo" class="external-link" target="_blank">**Dev.to**</a> или <a href="https://medium.com/@tiangolo" class="external-link" target="_blank">**Medium**</a>.
* Читать другие идеи, статьи и о созданных мной инструментах.
* Подписаться, чтобы читать, когда я публикую что-то новое.
## Оставить сообщение в X (Twitter) о **FastAPI** { #tweet-about-fastapi }
<a href="https://x.com/compose/tweet?text=I'm loving @fastapi because... https://github.com/fastapi/fastapi" class="external-link" target="_blank">Оставьте сообщение в X (Twitter) о **FastAPI**</a> и позвольте мне и другим узнать, почему он Вам нравится. 🎉
Я люблю узнавать о том, как **FastAPI** используется, что Вам понравилось в нём, в каких проектах/компаниях Вы его используете и т.д.
## Оставить голос за FastAPI { #vote-for-fastapi }
* <a href="https://www.slant.co/options/34241/~fastapi-review" class="external-link" target="_blank">Голосуйте за **FastAPI** в Slant</a>.
* <a href="https://alternativeto.net/software/fastapi/about/" class="external-link" target="_blank">Голосуйте за **FastAPI** в AlternativeTo</a>.
* <a href="https://stackshare.io/pypi-fastapi" class="external-link" target="_blank">Расскажите, что Вы используете **FastAPI** на StackShare</a>.
## Помочь другим с вопросами на GitHub { #help-others-with-questions-in-github }
Вы можете попробовать помочь другим с их вопросами в:
* <a href="https://github.com/fastapi/fastapi/discussions/categories/questions?discussions_q=category%3AQuestions+is%3Aunanswered" class="external-link" target="_blank">GitHub Discussions</a>
* <a href="https://github.com/fastapi/fastapi/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Aquestion+-label%3Aanswered+" class="external-link" target="_blank">GitHub Issues</a>
Во многих случаях Вы уже можете знать ответы на эти вопросы. 🤓
Если Вы много помогаете людям с их вопросами, Вы станете официальным [Экспертом FastAPI](fastapi-people.md#fastapi-experts){.internal-link target=_blank}. 🎉
Только помните, самое важное — постарайтесь быть добрыми. Люди приходят со своими разочарованиями и часто задают вопросы не лучшим образом, но постарайтесь, насколько можете, быть доброжелательными. 🤗
Идея сообщества **FastAPI** — быть доброжелательным и гостеприимным. В то же время не допускайте травлю или неуважительное поведение по отношению к другим. Мы должны заботиться друг о друге.
---
Как помочь другим с вопросами (в обсуждениях или Issues):
### Понять вопрос { #understand-the-question }
* Убедитесь, что поняли **цель** и кейс использования задающего вопрос.
* Затем проверьте, что вопрос (в подавляющем большинстве это вопросы) сформулирован **ясно**.
* Во многих случаях спрашивают о воображаемом решении пользователя, но может быть решение **получше**. Если Вы лучше поймёте проблему и кейс, сможете предложить **альтернативное решение**.
* Если вопрос непонятен, запросите больше **деталей**.
### Воспроизвести проблему { #reproduce-the-problem }
В большинстве случаев и вопросов есть что-то связанное с **исходным кодом** автора.
Во многих случаях предоставляют только фрагмент кода, но этого недостаточно, чтобы **воспроизвести проблему**.
* Попросите предоставить <a href="https://stackoverflow.com/help/minimal-reproducible-example" class="external-link" target="_blank">минимальный воспроизводимый пример</a>, который Вы сможете **скопировать-вставить** и запустить локально, чтобы увидеть ту же ошибку или поведение, или лучше понять их кейс.
* Если чувствуете себя особенно великодушными, можете попытаться **создать такой пример** сами, основываясь только на описании проблемы. Просто помните, что это может занять много времени, и, возможно, сначала лучше попросить уточнить проблему.
### Предложить решение { #suggest-solutions }
* После того как Вы поняли вопрос, Вы можете дать возможный **ответ**.
* Во многих случаях лучше понять **исходную проблему или кейс**, потому что может существовать способ решить её лучше, чем то, что пытаются сделать.
### Попросить закрыть { #ask-to-close }
Если Вам ответили, велика вероятность, что Вы решили их проблему, поздравляю, **Вы — герой**! 🦸
* Теперь, если проблема решена, можно попросить их:
* В GitHub Discussions: пометить комментарий как **answer** (ответ).
* В GitHub Issues: **закрыть** Issue.
## Отслеживать репозиторий на GitHub { #watch-the-github-repository }
Вы можете "отслеживать" FastAPI на GitHub (кликнув по кнопке "watch" наверху справа): <a href="https://github.com/fastapi/fastapi" class="external-link" target="_blank">https://github.com/fastapi/fastapi</a>. 👀
Если Вы выберете "Watching" вместо "Releases only", то будете получать уведомления, когда кто-либо создаёт новый вопрос или Issue. Вы также можете указать, что хотите получать уведомления только о новых Issues, или обсуждениях, или пулл-реквестах и т.д.
Тогда Вы можете попробовать помочь им с решением этих вопросов.
## Задать вопросы { #ask-questions }
Вы можете <a href="https://github.com/fastapi/fastapi/discussions/new?category=questions" class="external-link" target="_blank">создать новый вопрос</a> в репозитории GitHub, например:
* Задать **вопрос** или спросить о **проблеме**.
* Предложить новую **возможность**.
**Заметка**: если Вы это сделаете, то я попрошу Вас также помогать другим. 😉
## Проверять пулл-реквесты { #review-pull-requests }
Вы можете помочь мне проверять пулл-реквесты других участников.
И, снова, постарайтесь быть доброжелательными. 🤗
---
О том, что нужно иметь в виду и как проверять пулл-реквест:
### Понять проблему { #understand-the-problem }
* Во-первых, убедитесь, что **поняли проблему**, которую пулл-реквест пытается решить. Возможно, это обсуждалось более подробно в GitHub Discussion или Issue.
* Также есть вероятность, что пулл-реквест не нужен, так как проблему можно решить **другим путём**. Тогда Вы можете предложить или спросить об этом.
### Не переживайте о стиле { #dont-worry-about-style }
* Не стоит слишком беспокоиться о таких вещах, как стиль сообщений в коммитах — при слиянии я выполню squash и настрою коммит вручную.
* Также не беспокойтесь о правилах стиля, это уже проверяют автоматизированные инструменты.
Если будет нужна какая-то другая стилистика или единообразие, я попрошу об этом напрямую или добавлю поверх свои коммиты с нужными изменениями.
### Проверить код { #check-the-code }
* Проверьте и прочитайте код, посмотрите, логичен ли он, **запустите его локально** и проверьте, действительно ли он решает проблему.
* Затем оставьте **комментарий**, что Вы это сделали, так я пойму, что Вы действительно проверили код.
/// info | Информация
К сожалению, я не могу просто доверять PR-ам только потому, что у них есть несколько одобрений.
Несколько раз было так, что у PR-ов было 3, 5 или больше одобрений, вероятно из-за привлекательного описания, но когда я их проверял, они оказывались сломанными, содержали баги или вовсе не решали заявленную проблему. 😅
Поэтому очень важно действительно прочитать и запустить код и сообщить мне об этом в комментарии. 🤓
///
* Если PR можно упростить, Вы можете попросить об этом, но не нужно быть слишком придирчивым — может быть много субъективных мнений (и у меня тоже 🙈), поэтому лучше сосредоточиться на фундаментальных вещах.
### Тестировать { #tests }
* Помогите мне проверить, что у PR есть **тесты**.
* Проверьте, что тесты **падают** до PR. 🚨
* Затем проверьте, что тесты **проходят** после PR. ✅
* Многие PR не имеют тестов — Вы можете **напомнить** добавить тесты или даже **предложить** некоторые тесты сами. Это одна из самых трудозатратных частей, и здесь Вы можете очень помочь.
* Затем добавьте комментарий, что Вы попробовали, чтобы я знал, что Вы это проверили. 🤓
## Создать пулл-реквест { #create-a-pull-request }
Вы можете [сделать вклад](contributing.md){.internal-link target=_blank} в исходный код пулл-реквестами, например:
* Исправить опечатку, найденную в документации.
* Поделиться статьёй, видео или подкастом о FastAPI, которые Вы создали или нашли, <a href="https://github.com/fastapi/fastapi/edit/master/docs/en/data/external_links.yml" class="external-link" target="_blank">изменив этот файл</a>.
* Убедитесь, что добавили свою ссылку в начало соответствующего раздела.
* Помочь с [переводом документации](contributing.md#translations){.internal-link target=_blank} на Ваш язык.
* Вы также можете проверять переводы, сделанные другими.
* Предложить новые разделы документации.
* Исправить существующую проблему/баг.
* Убедитесь, что добавили тесты.
* Добавить новую возможность.
* Убедитесь, что добавили тесты.
* Убедитесь, что добавили документацию, если это уместно.
## Помочь поддерживать FastAPI { #help-maintain-fastapi }
Помогите мне поддерживать **FastAPI**! 🤓
Предстоит ещё много работы, и, по большей части, **ВЫ** можете её сделать.
Основные задачи, которые Вы можете выполнить прямо сейчас:
* [Помочь другим с вопросами на GitHub](#help-others-with-questions-in-github){.internal-link target=_blank} (смотрите секцию выше).
* [Проверять пулл-реквесты](#review-pull-requests){.internal-link target=_blank} (смотрите секцию выше).
Именно эти две задачи **забирают больше всего времени**. Это основная работа по поддержке FastAPI.
Если Вы можете помочь мне с этим, **Вы помогаете поддерживать FastAPI** и делаете так, чтобы он продолжал **развиваться быстрее и лучше**. 🚀
## Подключиться к чату { #join-the-chat }
Подключайтесь к 👥 <a href="https://discord.gg/VQjSZaeJmf" class="external-link" target="_blank">серверу чата в Discord</a> 👥 и общайтесь с другими участниками сообщества FastAPI.
/// tip | Подсказка
По вопросам — задавайте их в <a href="https://github.com/fastapi/fastapi/discussions/new?category=questions" class="external-link" target="_blank">GitHub Discussions</a>, так гораздо выше шанс, что Вы получите помощь от [Экспертов FastAPI](fastapi-people.md#fastapi-experts){.internal-link target=_blank}.
Используйте чат только для прочих общих бесед.
///
### Не используйте чат для вопросов { #dont-use-the-chat-for-questions }
Имейте в виду, что в чатах, благодаря "свободному общению", легко задать вопросы, которые слишком общие и на которые сложнее ответить, поэтому Вы можете не получить ответы.
На GitHub шаблон поможет Вам правильно сформулировать вопрос, чтобы Вам было легче получить хороший ответ или даже решить проблему самостоятельно ещё до того, как спросите. И на GitHub я могу следить за тем, чтобы всегда отвечать на всё, даже если это занимает время. А с чатами я не могу сделать этого лично. 😅
Кроме того, переписка в чатах хуже ищется, чем на GitHub, поэтому вопросы и ответы могут теряться среди остальных сообщений. И только те, что на GitHub, учитываются для получения лычки [Эксперт FastAPI](fastapi-people.md#fastapi-experts){.internal-link target=_blank}, так что вероятнее всего Вы получите больше внимания именно на GitHub.
С другой стороны, в чатах тысячи пользователей, так что почти всегда есть шанс найти там кого-то для разговора. 😄
## Спонсировать автора { #sponsor-the-author }
Если Ваш **продукт/компания** зависят от **FastAPI** или связаны с ним и Вы хотите донести до пользователей информацию о себе, Вы можете спонсировать автора (меня) через <a href="https://github.com/sponsors/tiangolo" class="external-link" target="_blank">GitHub Sponsors</a>. В зависимости от уровня поддержки Вы можете получить дополнительные бонусы, например, бейдж в документации. 🎁
---
Спасибо! 🚀
+79
View File
@@ -0,0 +1,79 @@
# История, проектирование и будущее { #history-design-and-future }
Однажды, <a href="https://github.com/fastapi/fastapi/issues/3#issuecomment-454956920" class="external-link" target="_blank">один из пользователей **FastAPI** задал вопрос</a>:
> Какова история этого проекта? Создаётся впечатление, что он явился из ниоткуда и завоевал мир за несколько недель [...]
Что ж, вот небольшая часть истории проекта.
## Альтернативы { #alternatives }
В течение нескольких лет я, возглавляя различные команды разработчиков, создавал довольно сложные API для машинного обучения, распределённых систем, асинхронных задач, баз данных NoSQL и т.д.
В рамках работы над этими проектами я исследовал, проверял и использовал многие фреймворки.
Во многом история **FastAPI** - история его предшественников.
Как написано в разделе [Альтернативы](alternatives.md){.internal-link target=_blank}:
<blockquote markdown="1">
**FastAPI** не существовал бы, если б не было более ранних работ других людей.
Они создали большое количество инструментов, которые и вдохновили меня на создание **FastAPI**.
Я всячески избегал создания нового фреймворка в течение нескольких лет. Сначала я пытался собрать все нужные возможности, которые ныне есть в **FastAPI**, используя множество различных фреймворков, плагинов и инструментов.
Но в какой-то момент не осталось другого выбора, кроме как создать что-то, что предоставляло бы все эти возможности сразу. Взять самые лучшие идеи из предыдущих инструментов и, используя введённые в Python аннотации типов (которых не было до версии 3.6), объединить их.
</blockquote>
## Исследования { #investigation }
Благодаря опыту использования существующих альтернатив, мы с коллегами изучили их основные идеи и скомбинировали собранные знания наилучшим образом.
Например, стало ясно, что необходимо брать за основу стандартные аннотации типов Python.
Также наилучшим подходом является использование уже существующих стандартов.
Итак, прежде чем приступить к написанию **FastAPI**, я потратил несколько месяцев на изучение OpenAPI, JSON Schema, OAuth2, и т.п. для понимания их взаимосвязей, совпадений и различий.
## Проектирование { #design }
Затем я потратил некоторое время на придумывание "API" разработчика, который я хотел иметь как пользователь (как разработчик, использующий FastAPI).
Я проверил несколько идей на самых популярных редакторах кода: PyCharm, VS Code, редакторы на базе Jedi.
Данные по редакторам я взял из <a href="https://www.jetbrains.com/research/python-developers-survey-2018/#development-tools" class="external-link" target="_blank">опроса Python-разработчиков</a>, который охватывает около 80% пользователей.
Это означает, что **FastAPI** был специально проверен на редакторах, используемых 80% Python-разработчиками. И поскольку большинство других редакторов, как правило, работают аналогичным образом, все его преимущества должны работать практически для всех редакторов.
Таким образом, я смог найти наилучшие способы сократить дублирование кода, обеспечить повсеместное автозавершение, проверку типов и ошибок и т.д.
И все это, чтобы все пользователи могли получать наилучший опыт разработки.
## Зависимости { #requirements }
Протестировав несколько вариантов, я решил, что в качестве основы буду использовать <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">**Pydantic**</a> и его преимущества.
По моим предложениям был изменён код этого фреймворка, чтобы сделать его полностью совместимым с JSON Schema, поддержать различные способы определения ограничений и улучшить поддержку в редакторах кода (проверки типов, автозавершение) на основе тестов в нескольких редакторах.
В то же время, я принимал участие в разработке <a href="https://www.starlette.dev/" class="external-link" target="_blank">**Starlette**</a>, ещё один из основных компонентов FastAPI.
## Разработка { #development }
К тому времени, когда я начал создавать **FastAPI**, большинство необходимых деталей уже существовало, дизайн был определён, зависимости и прочие инструменты были готовы, а знания о стандартах и спецификациях были четкими и свежими.
## Будущее { #future }
Сейчас уже ясно, что **FastAPI** со своими идеями стал полезен многим людям.
При сравнении с альтернативами, выбор падает на него, поскольку он лучше подходит для множества вариантов использования.
Многие разработчики и команды уже используют **FastAPI** в своих проектах (включая меня и мою команду).
Но, тем не менее, грядёт добавление ещё многих улучшений и возможностей.
У **FastAPI** великое будущее.
И [ваш вклад в это](help-fastapi.md){.internal-link target=_blank} - очень ценнен.
@@ -0,0 +1,56 @@
# Условный OpenAPI { #conditional-openapi }
При необходимости вы можете использовать настройки и переменные окружения, чтобы условно настраивать OpenAPI в зависимости от окружения и даже полностью его отключать.
## О безопасности, API и документации { #about-security-apis-and-docs }
Скрытие пользовательских интерфейсов документации в продакшн *не должно* быть способом защиты вашего API.
Это не добавляет дополнительной безопасности вашему API, *операции пути* (обработчики пути) всё равно будут доступны по своим путям.
Если в вашем коде есть уязвимость, она всё равно останется.
Сокрытие документации лишь усложняет понимание того, как взаимодействовать с вашим API, и может усложнить его отладку в продакшн. Это можно считать просто разновидностью <a href="https://en.wikipedia.org/wiki/Security_through_obscurity" class="external-link" target="_blank">безопасности через сокрытие</a>.
Если вы хотите обезопасить свой API, есть несколько более эффективных вещей, которые можно сделать, например:
* Убедитесь, что у вас чётко определены Pydantic-модели для тел запросов и ответов.
* Настройте необходимые разрешения и роли с помощью зависимостей.
* Никогда не храните пароли в открытом виде, только хэши паролей.
* Реализуйте и используйте известные криптографические инструменты, например pwdlib и JWT-токены, и т.д.
* Добавьте более тонкое управление доступом с помощью OAuth2 scopes (областей) там, где это необходимо.
* ...и т.п.
Тем не менее, у вас может быть очень специфичный случай использования, когда действительно нужно отключить документацию API для некоторых окружений (например, в продакшн) или в зависимости от настроек из переменных окружения.
## Условный OpenAPI из настроек и переменных окружения { #conditional-openapi-from-settings-and-env-vars }
Вы можете легко использовать те же настройки Pydantic, чтобы настроить сгенерированный OpenAPI и интерфейсы документации.
Например:
{* ../../docs_src/conditional_openapi/tutorial001.py hl[6,11] *}
Здесь мы объявляем настройку `openapi_url` с тем же значением по умолчанию — `"/openapi.json"`.
Затем используем её при создании приложения FastAPI.
Далее вы можете отключить OpenAPI (включая интерфейсы документации), установив переменную окружения `OPENAPI_URL` в пустую строку, например:
<div class="termy">
```console
$ OPENAPI_URL= uvicorn main:app
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
После этого, если перейти по адресам `/openapi.json`, `/docs` или `/redoc`, вы получите ошибку `404 Not Found`, например:
```JSON
{
"detail": "Not Found"
}
```
@@ -0,0 +1,70 @@
# Настройка Swagger UI { #configure-swagger-ui }
Вы можете настроить дополнительные <a href="https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/" class="external-link" target="_blank">параметры Swagger UI</a>.
Чтобы настроить их, передайте аргумент `swagger_ui_parameters` при создании объекта приложения `FastAPI()` или в функцию `get_swagger_ui_html()`.
`swagger_ui_parameters` принимает словарь с настройками, которые передаются в Swagger UI напрямую.
FastAPI преобразует эти настройки в **JSON**, чтобы они были совместимы с JavaScript, поскольку именно это требуется Swagger UI.
## Отключить подсветку синтаксиса { #disable-syntax-highlighting }
Например, вы можете отключить подсветку синтаксиса в Swagger UI.
Без изменения настроек подсветка синтаксиса включена по умолчанию:
<img src="/img/tutorial/extending-openapi/image02.png">
Но вы можете отключить её, установив `syntaxHighlight` в `False`:
{* ../../docs_src/configure_swagger_ui/tutorial001.py hl[3] *}
…и после этого Swagger UI больше не будет показывать подсветку синтаксиса:
<img src="/img/tutorial/extending-openapi/image03.png">
## Изменить тему { #change-the-theme }
Аналогично вы можете задать тему подсветки синтаксиса с ключом "syntaxHighlight.theme" (обратите внимание, что посередине стоит точка):
{* ../../docs_src/configure_swagger_ui/tutorial002.py hl[3] *}
Эта настройка изменит цветовую тему подсветки синтаксиса:
<img src="/img/tutorial/extending-openapi/image04.png">
## Изменить параметры Swagger UI по умолчанию { #change-default-swagger-ui-parameters }
FastAPI включает некоторые параметры конфигурации по умолчанию, подходящие для большинства случаев.
Это включает следующие настройки по умолчанию:
{* ../../fastapi/openapi/docs.py ln[8:23] hl[17:23] *}
Вы можете переопределить любую из них, указав другое значение в аргументе `swagger_ui_parameters`.
Например, чтобы отключить `deepLinking`, можно передать такие настройки в `swagger_ui_parameters`:
{* ../../docs_src/configure_swagger_ui/tutorial003.py hl[3] *}
## Другие параметры Swagger UI { #other-swagger-ui-parameters }
Чтобы увидеть все остальные возможные настройки, прочитайте официальную <a href="https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/" class="external-link" target="_blank">документацию по параметрам Swagger UI</a>.
## Настройки только для JavaScript { #javascript-only-settings }
Swagger UI также допускает другие настройки, которые являются **чисто JavaScript-объектами** (например, JavaScript-функциями).
FastAPI также включает следующие настройки `presets` (только для JavaScript):
```JavaScript
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIBundle.SwaggerUIStandalonePreset
]
```
Это объекты **JavaScript**, а не строки, поэтому напрямую передать их из Python-кода нельзя.
Если вам нужны такие настройки только для JavaScript, используйте один из методов выше. Переопределите *операцию пути* Swagger UI и вручную напишите любой необходимый JavaScript.
@@ -0,0 +1,185 @@
# Свои статические ресурсы UI документации (самостоятельный хостинг) { #custom-docs-ui-static-assets-self-hosting }
Документация API использует **Swagger UI** и **ReDoc**, и для каждого из них нужны некоторые файлы JavaScript и CSS.
По умолчанию эти файлы отдаются с <abbr title="Content Delivery Network – Сеть доставки контента: Сервис, обычно состоящий из нескольких серверов, который предоставляет статические файлы, такие как JavaScript и CSS. Обычно используется, чтобы отдавать эти файлы с сервера, расположенного ближе к клиенту, что улучшает производительность.">CDN</abbr>.
Но это можно настроить: вы можете указать конкретный CDN или отдавать файлы самостоятельно.
## Пользовательский CDN для JavaScript и CSS { #custom-cdn-for-javascript-and-css }
Допустим, вы хотите использовать другой <abbr title="Content Delivery Network – Сеть доставки контента">CDN</abbr>, например `https://unpkg.com/`.
Это может быть полезно, если, например, вы живёте в стране, где некоторые URL ограничены.
### Отключить автоматическую документацию { #disable-the-automatic-docs }
Первый шаг — отключить автоматическую документацию, так как по умолчанию она использует стандартный CDN.
Чтобы отключить её, установите их URL в значение `None` при создании вашего приложения `FastAPI`:
{* ../../docs_src/custom_docs_ui/tutorial001.py hl[8] *}
### Подключить пользовательскую документацию { #include-the-custom-docs }
Теперь вы можете создать *операции пути* для пользовательской документации.
Вы можете переиспользовать внутренние функции FastAPI для создания HTML-страниц документации и передать им необходимые аргументы:
* `openapi_url`: URL, по которому HTML-страница документации сможет получить схему OpenAPI для вашего API. Здесь можно использовать атрибут `app.openapi_url`.
* `title`: заголовок вашего API.
* `oauth2_redirect_url`: здесь можно использовать `app.swagger_ui_oauth2_redirect_url`, чтобы оставить значение по умолчанию.
* `swagger_js_url`: URL, по которому HTML для документации Swagger UI сможет получить файл **JavaScript**. Это URL вашего пользовательского CDN.
* `swagger_css_url`: URL, по которому HTML для документации Swagger UI сможет получить файл **CSS**. Это URL вашего пользовательского CDN.
Аналогично и для ReDoc...
{* ../../docs_src/custom_docs_ui/tutorial001.py hl[2:6,11:19,22:24,27:33] *}
/// tip | Совет
*Операция пути* для `swagger_ui_redirect` — это вспомогательный эндпоинт на случай, когда вы используете OAuth2.
Если вы интегрируете свой API с провайдером OAuth2, вы сможете аутентифицироваться и вернуться к документации API с полученными учётными данными, а затем взаимодействовать с ним, используя реальную аутентификацию OAuth2.
Swagger UI сделает это за вас «за кулисами», но для этого ему нужен этот вспомогательный «redirect» эндпоинт.
///
### Создайте *операцию пути*, чтобы проверить { #create-a-path-operation-to-test-it }
Чтобы убедиться, что всё работает, создайте *операцию пути*:
{* ../../docs_src/custom_docs_ui/tutorial001.py hl[36:38] *}
### Тестирование { #test-it }
Теперь вы должны иметь возможность открыть свою документацию по адресу <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a> и перезагрузить страницу — «ассеты» (статические файлы) будут загружаться с нового CDN.
## Самостоятельный хостинг JavaScript и CSS для документации { #self-hosting-javascript-and-css-for-docs }
Самостоятельный хостинг JavaScript и CSS может быть полезен, если, например, вам нужно, чтобы приложение продолжало работать в офлайне, без доступа к открытому Интернету, или в локальной сети.
Здесь вы увидите, как отдавать эти файлы самостоятельно, в том же приложении FastAPI, и настроить документацию на их использование.
### Структура файлов проекта { #project-file-structure }
Допустим, структура файлов вашего проекта выглядит так:
```
.
├── app
│ ├── __init__.py
│ ├── main.py
```
Теперь создайте директорию для хранения этих статических файлов.
Новая структура файлов может выглядеть так:
```
.
├── app
│   ├── __init__.py
│   ├── main.py
└── static/
```
### Скачайте файлы { #download-the-files }
Скачайте статические файлы, необходимые для документации, и поместите их в директорию `static/`.
Скорее всего, вы можете кликнуть правой кнопкой на каждой ссылке и выбрать что-то вроде «Сохранить ссылку как...».
**Swagger UI** использует файлы:
* <a href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js" class="external-link" target="_blank">`swagger-ui-bundle.js`</a>
* <a href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css" class="external-link" target="_blank">`swagger-ui.css`</a>
А **ReDoc** использует файл:
* <a href="https://cdn.jsdelivr.net/npm/redoc@2/bundles/redoc.standalone.js" class="external-link" target="_blank">`redoc.standalone.js`</a>
После этого структура файлов может выглядеть так:
```
.
├── app
│   ├── __init__.py
│   ├── main.py
└── static
├── redoc.standalone.js
├── swagger-ui-bundle.js
└── swagger-ui.css
```
### Предоставьте доступ к статическим файлам { #serve-the-static-files }
* Импортируйте `StaticFiles`.
* Смонтируйте экземпляр `StaticFiles()` в определённый путь.
{* ../../docs_src/custom_docs_ui/tutorial002.py hl[7,11] *}
### Протестируйте статические файлы { #test-the-static-files }
Запустите своё приложение и откройте <a href="http://127.0.0.1:8000/static/redoc.standalone.js" class="external-link" target="_blank">http://127.0.0.1:8000/static/redoc.standalone.js</a>.
Вы должны увидеть очень длинный JavaScript-файл для **ReDoc**.
Он может начинаться примерно так:
```JavaScript
/*! For license information please see redoc.standalone.js.LICENSE.txt */
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("null")):
...
```
Это подтверждает, что ваше приложение умеет отдавать статические файлы и что вы поместили файлы документации в нужное место.
Теперь можно настроить приложение так, чтобы документация использовала эти статические файлы.
### Отключить автоматическую документацию для статических файлов { #disable-the-automatic-docs-for-static-files }
Так же, как и при использовании пользовательского CDN, первым шагом будет отключение автоматической документации, так как по умолчанию она использует CDN.
Чтобы отключить её, установите их URL в значение `None` при создании вашего приложения `FastAPI`:
{* ../../docs_src/custom_docs_ui/tutorial002.py hl[9] *}
### Подключить пользовательскую документацию со статическими файлами { #include-the-custom-docs-for-static-files }
Аналогично пользовательскому CDN, теперь вы можете создать *операции пути* для собственной документации.
Снова можно переиспользовать внутренние функции FastAPI для создания HTML-страниц документации и передать им необходимые аргументы:
* `openapi_url`: URL, по которому HTML-страница документации сможет получить схему OpenAPI для вашего API. Здесь можно использовать атрибут `app.openapi_url`.
* `title`: заголовок вашего API.
* `oauth2_redirect_url`: здесь можно использовать `app.swagger_ui_oauth2_redirect_url`, чтобы оставить значение по умолчанию.
* `swagger_js_url`: URL, по которому HTML для документации Swagger UI сможет получить файл **JavaScript**. **Это тот файл, который теперь отдаёт ваше собственное приложение**.
* `swagger_css_url`: URL, по которому HTML для документации Swagger UI сможет получить файл **CSS**. **Это тот файл, который теперь отдаёт ваше собственное приложение**.
Аналогично и для ReDoc...
{* ../../docs_src/custom_docs_ui/tutorial002.py hl[2:6,14:22,25:27,30:36] *}
/// tip | Совет
*Операция пути* для `swagger_ui_redirect` — это вспомогательный эндпоинт на случай, когда вы используете OAuth2.
Если вы интегрируете свой API с провайдером OAuth2, вы сможете аутентифицироваться и вернуться к документации API с полученными учётными данными, а затем взаимодействовать с ним, используя реальную аутентификацию OAuth2.
Swagger UI сделает это за вас «за кулисами», но для этого ему нужен этот вспомогательный «redirect» эндпоинт.
///
### Создайте *операцию пути* для теста статических файлов { #create-a-path-operation-to-test-static-files }
Чтобы убедиться, что всё работает, создайте *операцию пути*:
{* ../../docs_src/custom_docs_ui/tutorial002.py hl[39:41] *}
### Тестирование UI со статическими файлами { #test-static-files-ui }
Теперь вы можете отключить Wi‑Fi, открыть свою документацию по адресу <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a> и перезагрузить страницу.
Даже без Интернета вы сможете видеть документацию к своему API и взаимодействовать с ним.
@@ -0,0 +1,109 @@
# Пользовательские классы Request и APIRoute { #custom-request-and-apiroute-class }
В некоторых случаях может понадобиться переопределить логику, используемую классами `Request` и `APIRoute`.
В частности, это может быть хорошей альтернативой логике в middleware.
Например, если вы хотите прочитать или изменить тело запроса до того, как оно будет обработано вашим приложением.
/// danger | Опасность
Это «продвинутая» возможность.
Если вы только начинаете работать с **FastAPI**, возможно, стоит пропустить этот раздел.
///
## Сценарии использования { #use-cases }
Некоторые сценарии:
* Преобразование тел запросов, не в формате JSON, в JSON (например, <a href="https://msgpack.org/index.html" class="external-link" target="_blank">`msgpack`</a>).
* Распаковка тел запросов, сжатых с помощью gzip.
* Автоматическое логирование всех тел запросов.
## Обработка пользовательского кодирования тела запроса { #handling-custom-request-body-encodings }
Посмотрим как использовать пользовательский подкласс `Request` для распаковки gzip-запросов.
И подкласс `APIRoute`, чтобы использовать этот пользовательский класс запроса.
### Создать пользовательский класс `GzipRequest` { #create-a-custom-gziprequest-class }
/// tip | Совет
Это учебный пример, демонстрирующий принцип работы. Если вам нужна поддержка Gzip, вы можете использовать готовый [`GzipMiddleware`](../advanced/middleware.md#gzipmiddleware){.internal-link target=_blank}.
///
Сначала создадим класс `GzipRequest`, который переопределит метод `Request.body()` и распакует тело запроса при наличии соответствующего HTTP-заголовка.
Если в заголовке нет `gzip`, он не будет пытаться распаковывать тело.
Таким образом, один и тот же класс маршрута сможет обрабатывать как gzip-сжатые, так и несжатые запросы.
{* ../../docs_src/custom_request_and_route/tutorial001.py hl[8:15] *}
### Создать пользовательский класс `GzipRoute` { #create-a-custom-gziproute-class }
Далее создадим пользовательский подкласс `fastapi.routing.APIRoute`, который будет использовать `GzipRequest`.
На этот раз он переопределит метод `APIRoute.get_route_handler()`.
Этот метод возвращает функцию. Именно эта функция получает HTTP-запрос и возвращает HTTP-ответ.
Здесь мы используем её, чтобы создать `GzipRequest` из исходного HTTP-запроса.
{* ../../docs_src/custom_request_and_route/tutorial001.py hl[18:26] *}
/// note | Технические детали
У `Request` есть атрибут `request.scope` — это просто Python-`dict`, содержащий метаданные, связанные с HTTP-запросом.
У `Request` также есть `request.receive` — функция для «получения» тела запроса.
И `dict` `scope`, и функция `receive` являются частью спецификации ASGI.
Именно этих двух компонентов — `scope` и `receive` — достаточно, чтобы создать новый экземпляр `Request`.
Чтобы узнать больше о `Request`, см. <a href="https://www.starlette.dev/requests/" class="external-link" target="_blank">документацию Starlette о запросах</a>.
///
Единственное, что делает по-другому функция, возвращённая `GzipRequest.get_route_handler`, — преобразует `Request` в `GzipRequest`.
Благодаря этому наш `GzipRequest` позаботится о распаковке данных (при необходимости) до передачи их в наши *операции пути*.
Дальше вся логика обработки остаётся прежней.
Но благодаря изменениям в `GzipRequest.body` тело запроса будет автоматически распаковано при необходимости, когда оно будет загружено **FastAPI**.
## Доступ к телу запроса в обработчике исключений { #accessing-the-request-body-in-an-exception-handler }
/// tip | Совет
Для решения этой задачи, вероятно, намного проще использовать `body` в пользовательском обработчике `RequestValidationError` ([Обработка ошибок](../tutorial/handling-errors.md#use-the-requestvalidationerror-body){.internal-link target=_blank}).
Но этот пример всё равно актуален и показывает, как взаимодействовать с внутренними компонентами.
///
Тем же подходом можно воспользоваться, чтобы получить доступ к телу запроса в обработчике исключений.
Нужно лишь обработать запрос внутри блока `try`/`except`:
{* ../../docs_src/custom_request_and_route/tutorial002.py hl[13,15] *}
Если произойдёт исключение, экземпляр `Request` всё ещё будет в области видимости, поэтому мы сможем прочитать тело запроса и использовать его при обработке ошибки:
{* ../../docs_src/custom_request_and_route/tutorial002.py hl[16:18] *}
## Пользовательский класс `APIRoute` в роутере { #custom-apiroute-class-in-a-router }
Вы также можете задать параметр `route_class` у `APIRouter`:
{* ../../docs_src/custom_request_and_route/tutorial003.py hl[26] *}
В этом примере *операции пути*, объявленные в `router`, будут использовать пользовательский класс `TimedRoute` и получат дополнительный HTTP-заголовок `X-Response-Time` в ответе с временем, затраченным на формирование ответа:
{* ../../docs_src/custom_request_and_route/tutorial003.py hl[13:20] *}
+80
View File
@@ -0,0 +1,80 @@
# Расширение OpenAPI { #extending-openapi }
Иногда может понадобиться изменить сгенерированную схему OpenAPI.
В этом разделе показано, как это сделать.
## Обычный процесс { #the-normal-process }
Обычный (по умолчанию) процесс выглядит так.
Приложение `FastAPI` (экземпляр) имеет метод `.openapi()`, который должен возвращать схему OpenAPI.
В процессе создания объекта приложения регистрируется *операция пути* (обработчик пути) для `/openapi.json` (или для того, что указано в вашем `openapi_url`).
Она просто возвращает JSON-ответ с результатом вызова метода приложения `.openapi()`.
По умолчанию метод `.openapi()` проверяет свойство `.openapi_schema`: если в нём уже есть данные, возвращает их.
Если нет — генерирует схему с помощью вспомогательной функции `fastapi.openapi.utils.get_openapi`.
Функция `get_openapi()` принимает параметры:
* `title`: Заголовок OpenAPI, отображается в документации.
* `version`: Версия вашего API, например `2.5.0`.
* `openapi_version`: Версия используемой спецификации OpenAPI. По умолчанию — последняя: `3.1.0`.
* `summary`: Краткое описание API.
* `description`: Описание вашего API; может включать Markdown и будет отображается в документации.
* `routes`: Список маршрутов — это каждая зарегистрированная *операция пути*. Берутся из `app.routes`.
/// info | Информация
Параметр `summary` доступен в OpenAPI 3.1.0 и выше, поддерживается FastAPI версии 0.99.0 и выше.
///
## Переопределение значений по умолчанию { #overriding-the-defaults }
Используя информацию выше, вы можете той же вспомогательной функцией сгенерировать схему OpenAPI и переопределить любые нужные части.
Например, добавим <a href="https://github.com/Rebilly/ReDoc/blob/master/docs/redoc-vendor-extensions.md#x-logo" class="external-link" target="_blank">расширение OpenAPI ReDoc для включения собственного логотипа</a>.
### Обычный **FastAPI** { #normal-fastapi }
Сначала напишите приложение **FastAPI** как обычно:
{* ../../docs_src/extending_openapi/tutorial001.py hl[1,4,7:9] *}
### Сгенерируйте схему OpenAPI { #generate-the-openapi-schema }
Затем используйте ту же вспомогательную функцию для генерации схемы OpenAPI внутри функции `custom_openapi()`:
{* ../../docs_src/extending_openapi/tutorial001.py hl[2,15:21] *}
### Измените схему OpenAPI { #modify-the-openapi-schema }
Теперь можно добавить расширение ReDoc, добавив кастомный `x-logo` в «объект» `info` в схеме OpenAPI:
{* ../../docs_src/extending_openapi/tutorial001.py hl[22:24] *}
### Кэшируйте схему OpenAPI { #cache-the-openapi-schema }
Вы можете использовать свойство `.openapi_schema` как «кэш» для хранения сгенерированной схемы.
Так приложению не придётся генерировать схему каждый раз, когда пользователь открывает документацию API.
Она будет создана один раз, а затем тот же кэшированный вариант будет использоваться для последующих запросов.
{* ../../docs_src/extending_openapi/tutorial001.py hl[13:14,25:26] *}
### Переопределите метод { #override-the-method }
Теперь вы можете заменить метод `.openapi()` на вашу новую функцию.
{* ../../docs_src/extending_openapi/tutorial001.py hl[29] *}
### Проверьте { #check-it }
Перейдите на <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a> — вы увидите, что используется ваш кастомный логотип (в этом примере — логотип **FastAPI**):
<img src="/img/tutorial/extending-openapi/image01.png">
+39
View File
@@ -0,0 +1,39 @@
# Общее — Как сделать — Рецепты { #general-how-to-recipes }
Здесь несколько указателей на другие места в документации для общих или частых вопросов.
## Фильтрация данных — Безопасность { #filter-data-security }
Чтобы убедиться, что вы не возвращаете больше данных, чем следует, прочитайте документацию: [Руководство — Модель ответа — Возвращаемый тип](../tutorial/response-model.md){.internal-link target=_blank}.
## Теги в документации — OpenAPI { #documentation-tags-openapi }
Чтобы добавить теги к вашим *операциям пути* и группировать их в интерфейсе документации, прочитайте документацию: [Руководство — Конфигурации операций пути — Теги](../tutorial/path-operation-configuration.md#tags){.internal-link target=_blank}.
## Краткое описание и описание в документации — OpenAPI { #documentation-summary-and-description-openapi }
Чтобы добавить краткое описание и описание к вашим *операциям пути* и отобразить их в интерфейсе документации, прочитайте документацию: [Руководство — Конфигурации операций пути — Краткое описание и описание](../tutorial/path-operation-configuration.md#summary-and-description){.internal-link target=_blank}.
## Описание ответа в документации — OpenAPI { #documentation-response-description-openapi }
Чтобы задать описание ответа, отображаемое в интерфейсе документации, прочитайте документацию: [Руководство — Конфигурации операций пути — Описание ответа](../tutorial/path-operation-configuration.md#response-description){.internal-link target=_blank}.
## Документация — пометить операцию пути устаревшей — OpenAPI { #documentation-deprecate-a-path-operation-openapi }
Чтобы пометить *операцию пути* как устаревшую и показать это в интерфейсе документации, прочитайте документацию: [Руководство — Конфигурации операций пути — Пометить операцию пути устаревшей](../tutorial/path-operation-configuration.md#deprecate-a-path-operation){.internal-link target=_blank}.
## Преобразование любых данных к формату, совместимому с JSON { #convert-any-data-to-json-compatible }
Чтобы преобразовать любые данные к формату, совместимому с JSON, прочитайте документацию: [Руководство — JSON-совместимый кодировщик](../tutorial/encoder.md){.internal-link target=_blank}.
## Метаданные OpenAPI — Документация { #openapi-metadata-docs }
Чтобы добавить метаданные в вашу схему OpenAPI, включая лицензию, версию, контакты и т.д., прочитайте документацию: [Руководство — Метаданные и URL документации](../tutorial/metadata.md){.internal-link target=_blank}.
## Пользовательский URL OpenAPI { #openapi-custom-url }
Чтобы настроить URL OpenAPI (или удалить его), прочитайте документацию: [Руководство — Метаданные и URL документации](../tutorial/metadata.md#openapi-url){.internal-link target=_blank}.
## URL документации OpenAPI { #openapi-docs-urls }
Чтобы изменить URL, используемые для автоматически сгенерированных пользовательских интерфейсов документации, прочитайте документацию: [Руководство — Метаданные и URL документации](../tutorial/metadata.md#docs-urls){.internal-link target=_blank}.
+60
View File
@@ -0,0 +1,60 @@
# GraphQL { #graphql }
Так как **FastAPI** основан на стандарте **ASGI**, очень легко интегрировать любую библиотеку **GraphQL**, также совместимую с ASGI.
Вы можете комбинировать обычные *операции пути* FastAPI с GraphQL в одном приложении.
/// tip | Совет
**GraphQL** решает некоторые очень специфические задачи.
У него есть как **преимущества**, так и **недостатки** по сравнению с обычными **веб-API**.
Убедитесь, что **выгоды** для вашего случая использования перевешивают **недостатки**. 🤓
///
## Библиотеки GraphQL { #graphql-libraries }
Ниже приведены некоторые библиотеки **GraphQL** с поддержкой **ASGI**. Их можно использовать с **FastAPI**:
* <a href="https://strawberry.rocks/" class="external-link" target="_blank">Strawberry</a> 🍓
* С <a href="https://strawberry.rocks/docs/integrations/fastapi" class="external-link" target="_blank">документацией для FastAPI</a>
* <a href="https://ariadnegraphql.org/" class="external-link" target="_blank">Ariadne</a>
* С <a href="https://ariadnegraphql.org/docs/fastapi-integration" class="external-link" target="_blank">документацией для FastAPI</a>
* <a href="https://tartiflette.io/" class="external-link" target="_blank">Tartiflette</a>
* С <a href="https://tartiflette.github.io/tartiflette-asgi/" class="external-link" target="_blank">Tartiflette ASGI</a> для интеграции с ASGI
* <a href="https://graphene-python.org/" class="external-link" target="_blank">Graphene</a>
* С <a href="https://github.com/ciscorn/starlette-graphene3" class="external-link" target="_blank">starlette-graphene3</a>
## GraphQL со Strawberry { #graphql-with-strawberry }
Если вам нужно или хочется работать с **GraphQL**, <a href="https://strawberry.rocks/" class="external-link" target="_blank">**Strawberry**</a> — **рекомендуемая** библиотека, так как её дизайн ближе всего к дизайну **FastAPI**, всё основано на **аннотациях типов**.
В зависимости от вашего сценария использования вы можете предпочесть другую библиотеку, но если бы вы спросили меня, я, скорее всего, предложил бы попробовать **Strawberry**.
Вот небольшой пример того, как можно интегрировать Strawberry с FastAPI:
{* ../../docs_src/graphql/tutorial001.py hl[3,22,25] *}
Подробнее о Strawberry можно узнать в <a href="https://strawberry.rocks/" class="external-link" target="_blank">документации Strawberry</a>.
А также в документации по <a href="https://strawberry.rocks/docs/integrations/fastapi" class="external-link" target="_blank">интеграции Strawberry с FastAPI</a>.
## Устаревший `GraphQLApp` из Starlette { #older-graphqlapp-from-starlette }
В предыдущих версиях Starlette был класс `GraphQLApp` для интеграции с <a href="https://graphene-python.org/" class="external-link" target="_blank">Graphene</a>.
Он был объявлен устаревшим в Starlette, но если у вас есть код, который его использовал, вы можете легко **мигрировать** на <a href="https://github.com/ciscorn/starlette-graphene3" class="external-link" target="_blank">starlette-graphene3</a>, который решает ту же задачу и имеет **почти идентичный интерфейс**.
/// tip | Совет
Если вам нужен GraphQL, я всё же рекомендую посмотреть <a href="https://strawberry.rocks/" class="external-link" target="_blank">Strawberry</a>, так как он основан на аннотациях типов, а не на пользовательских классах и типах.
///
## Подробнее { #learn-more }
Подробнее о **GraphQL** вы можете узнать в <a href="https://graphql.org/" class="external-link" target="_blank">официальной документации GraphQL</a>.
Также можно почитать больше о каждой из указанных выше библиотек по приведённым ссылкам.
+13
View File
@@ -0,0 +1,13 @@
# Как сделать — Рецепты { #how-to-recipes }
Здесь вы найдете разные рецепты и руководства «как сделать» по различным темам.
Большинство из этих идей более-менее независимы, и в большинстве случаев вам стоит изучать их только если они напрямую относятся к вашему проекту.
Если что-то кажется интересным и полезным для вашего проекта, смело изучайте; в противном случае, вероятно, можно просто пропустить.
/// tip | Совет
Если вы хотите изучить FastAPI структурированно (рекомендуется), вместо этого читайте [Учебник — Руководство пользователя](../tutorial/index.md){.internal-link target=_blank} по главам.
///
@@ -0,0 +1,133 @@
# Миграция с Pydantic v1 на Pydantic v2 { #migrate-from-pydantic-v1-to-pydantic-v2 }
Если у вас старое приложение FastAPI, возможно, вы используете Pydantic версии 1.
FastAPI поддерживает и Pydantic v1, и v2 начиная с версии 0.100.0.
Если у вас был установлен Pydantic v2, использовался он. Если вместо этого был установлен Pydantic v1 — использовался он.
Сейчас Pydantic v1 объявлен устаревшим, и поддержка его будет удалена в следующих версиях FastAPI, поэтому вам следует **перейти на Pydantic v2**. Так вы получите последние возможности, улучшения и исправления.
/// warning | Предупреждение
Кроме того, команда Pydantic прекратила поддержку Pydantic v1 для последних версий Python, начиная с **Python 3.14**.
Если вы хотите использовать последние возможности Python, вам нужно убедиться, что вы используете Pydantic v2.
///
Если у вас старое приложение FastAPI с Pydantic v1, здесь я покажу, как мигрировать на Pydantic v2, и **новые возможности в FastAPI 0.119.0**, которые помогут выполнить постепенную миграцию.
## Официальное руководство { #official-guide }
У Pydantic есть официальное <a href="https://docs.pydantic.dev/latest/migration/" class="external-link" target="_blank">руководство по миграции</a> с v1 на v2.
Там также описано, что изменилось, как валидации стали более корректными и строгими, возможные нюансы и т.д.
Прочитайте его, чтобы лучше понять, что изменилось.
## Тесты { #tests }
Убедитесь, что у вас есть [тесты](../tutorial/testing.md){.internal-link target=_blank} для вашего приложения и что вы запускаете их в системе непрерывной интеграции (CI).
Так вы сможете выполнить обновление и убедиться, что всё работает как ожидается.
## `bump-pydantic` { #bump-pydantic }
Во многих случаях, когда вы используете обычные Pydantic‑модели без пользовательских настроек, вы сможете автоматизировать большую часть процесса миграции с Pydantic v1 на Pydantic v2.
Вы можете использовать <a href="https://github.com/pydantic/bump-pydantic" class="external-link" target="_blank">`bump-pydantic`</a> от той же команды Pydantic.
Этот инструмент поможет автоматически внести большую часть необходимых изменений в код.
После этого запустите тесты и проверьте, что всё работает. Если да — на этом всё. 😎
## Pydantic v1 в v2 { #pydantic-v1-in-v2 }
Pydantic v2 включает всё из Pydantic v1 как подмодуль `pydantic.v1`.
Это означает, что вы можете установить последнюю версию Pydantic v2 и импортировать и использовать старые компоненты Pydantic v1 из этого подмодуля так, как если бы у вас был установлен старый Pydantic v1.
{* ../../docs_src/pydantic_v1_in_v2/tutorial001_an_py310.py hl[1,4] *}
### Поддержка FastAPI для Pydantic v1 внутри v2 { #fastapi-support-for-pydantic-v1-in-v2 }
Начиная с FastAPI 0.119.0, есть также частичная поддержка Pydantic v1 в составе Pydantic v2, чтобы упростить миграцию на v2.
Таким образом, вы можете обновить Pydantic до последней версии 2 и сменить импорты на подмодуль `pydantic.v1` — во многих случаях всё просто заработает.
{* ../../docs_src/pydantic_v1_in_v2/tutorial002_an_py310.py hl[2,5,15] *}
/// warning | Предупреждение
Имейте в виду, что так как команда Pydantic больше не поддерживает Pydantic v1 в последних версиях Python, начиная с Python 3.14, использование `pydantic.v1` также не поддерживается в Python 3.14 и выше.
///
### Pydantic v1 и v2 в одном приложении { #pydantic-v1-and-v2-on-the-same-app }
В Pydantic **не поддерживается** ситуация, когда в одной модели Pydantic v2 используются поля, определённые как модели Pydantic v1, и наоборот.
```mermaid
graph TB
subgraph "❌ Not Supported"
direction TB
subgraph V2["Pydantic v2 Model"]
V1Field["Pydantic v1 Model"]
end
subgraph V1["Pydantic v1 Model"]
V2Field["Pydantic v2 Model"]
end
end
style V2 fill:#f9fff3
style V1 fill:#fff6f0
style V1Field fill:#fff6f0
style V2Field fill:#f9fff3
```
…но в одном и том же приложении вы можете иметь отдельные модели на Pydantic v1 и v2.
```mermaid
graph TB
subgraph "✅ Supported"
direction TB
subgraph V2["Pydantic v2 Model"]
V2Field["Pydantic v2 Model"]
end
subgraph V1["Pydantic v1 Model"]
V1Field["Pydantic v1 Model"]
end
end
style V2 fill:#f9fff3
style V1 fill:#fff6f0
style V1Field fill:#fff6f0
style V2Field fill:#f9fff3
```
В некоторых случаях можно использовать и модели Pydantic v1, и v2 в одной и той же операции пути (обработчике пути) вашего приложения FastAPI:
{* ../../docs_src/pydantic_v1_in_v2/tutorial003_an_py310.py hl[2:3,6,12,21:22] *}
В примере выше модель входных данных — это модель Pydantic v1, а модель выходных данных (указанная в `response_model=ItemV2`) — это модель Pydantic v2.
### Параметры Pydantic v1 { #pydantic-v1-parameters }
Если вам нужно использовать некоторые специфичные для FastAPI инструменты для параметров, такие как `Body`, `Query`, `Form` и т.п., с моделями Pydantic v1, вы можете импортировать их из `fastapi.temp_pydantic_v1_params`, пока завершаете миграцию на Pydantic v2:
{* ../../docs_src/pydantic_v1_in_v2/tutorial004_an_py310.py hl[4,18] *}
### Мигрируйте по шагам { #migrate-in-steps }
/// tip | Совет
Сначала попробуйте `bump-pydantic`. Если тесты проходят и всё работает, вы справились одной командой. ✨
///
Если `bump-pydantic` не подходит для вашего случая, вы можете использовать поддержку одновременной работы моделей Pydantic v1 и v2 в одном приложении, чтобы мигрировать на Pydantic v2 постепенно.
Сначала обновите Pydantic до последней 2-й версии и измените импорты так, чтобы все ваши модели использовали `pydantic.v1`.
Затем начните мигрировать ваши модели с Pydantic v1 на v2 группами, поэтапно. 🚶
@@ -0,0 +1,104 @@
# Разделять схемы OpenAPI для входа и выхода или нет { #separate-openapi-schemas-for-input-and-output-or-not }
При использовании **Pydantic v2** сгенерированный OpenAPI становится чуть более точным и **корректным**, чем раньше. 😎
На самом деле, в некоторых случаях в OpenAPI будет даже **две JSON схемы** для одной и той же Pydantic‑модели: для входа и для выхода — в зависимости от наличия **значений по умолчанию**.
Посмотрим, как это работает, и как это изменить при необходимости.
## Pydantic‑модели для входа и выхода { #pydantic-models-for-input-and-output }
Предположим, у вас есть Pydantic‑модель со значениями по умолчанию, как здесь:
{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:7] hl[7] *}
### Модель для входа { #model-for-input }
Если использовать эту модель как входную, как здесь:
{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:15] hl[14] *}
…то поле `description` **не будет обязательным**, потому что у него значение по умолчанию `None`.
### Входная модель в документации { #input-model-in-docs }
В документации это видно: у поля `description` нет **красной звёздочки** — оно не отмечено как обязательное:
<div class="screenshot">
<img src="/img/tutorial/separate-openapi-schemas/image01.png">
</div>
### Модель для выхода { #model-for-output }
Но если использовать ту же модель как выходную, как здесь:
{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py hl[19] *}
…то, поскольку у `description` есть значение по умолчанию, даже если вы **ничего не вернёте** для этого поля, оно всё равно будет иметь это **значение по умолчанию**.
### Модель для данных ответа { #model-for-output-response-data }
Если поработать с интерактивной документацией и посмотреть ответ, то, хотя код ничего не добавил в одно из полей `description`, JSON‑ответ содержит значение по умолчанию (`null`):
<div class="screenshot">
<img src="/img/tutorial/separate-openapi-schemas/image02.png">
</div>
Это означает, что у него **всегда будет какое‑то значение**, просто иногда это значение может быть `None` (или `null` в JSON).
Следовательно, клиентам, использующим ваш API, не нужно проверять наличие этого значения: они могут **исходить из того, что поле всегда присутствует**, а в некоторых случаях имеет значение по умолчанию `None`.
В OpenAPI это описывается тем, что поле помечается как **обязательное**, поскольку оно всегда присутствует.
Из‑за этого JSON Schema для модели может отличаться в зависимости от использования для **входа** или **выхода**:
* для **входа** `description` не будет обязательным
* для **выхода** оно будет **обязательным** (и при этом может быть `None`, или, в терминах JSON, `null`)
### Выходная модель в документации { #model-for-output-in-docs }
В документации это тоже видно, что **оба**: `name` и `description`, помечены **красной звёздочкой** как **обязательные**:
<div class="screenshot">
<img src="/img/tutorial/separate-openapi-schemas/image03.png">
</div>
### Модели для входа и выхода в документации { #model-for-input-and-output-in-docs }
Если посмотреть все доступные схемы (JSON Schema) в OpenAPI, вы увидите две: `Item-Input` и `Item-Output`.
Для `Item-Input` поле `description` **не является обязательным** — красной звёздочки нет.
А для `Item-Output` `description` **обязательно** — красная звёздочка есть.
<div class="screenshot">
<img src="/img/tutorial/separate-openapi-schemas/image04.png">
</div>
Благодаря этой возможности **Pydantic v2** документация вашего API становится более **точной**; если у вас есть сгенерированные клиенты и SDK, они тоже будут точнее, с лучшим **удобством для разработчиков** и большей консистентностью. 🎉
## Не разделять схемы { #do-not-separate-schemas }
Однако бывают случаи, когда вы хотите иметь **одну и ту же схему для входа и выхода**.
Главный сценарий — когда у вас уже есть сгенерированный клиентский код/SDK, и вы пока не хотите обновлять весь этот автогенерируемый код/SDK (рано или поздно вы это сделаете, но не сейчас).
В таком случае вы можете отключить эту функциональность в FastAPI с помощью параметра `separate_input_output_schemas=False`.
/// info | Информация
Поддержка `separate_input_output_schemas` появилась в FastAPI `0.102.0`. 🤓
///
{* ../../docs_src/separate_openapi_schemas/tutorial002_py310.py hl[10] *}
### Одна и та же схема для входной и выходной моделей в документации { #same-schema-for-input-and-output-models-in-docs }
Теперь для этой модели будет одна общая схема и для входа, и для выхода — только `Item`, и в ней `description` будет **не обязательным**:
<div class="screenshot">
<img src="/img/tutorial/separate-openapi-schemas/image05.png">
</div>
Это то же поведение, что и в Pydantic v1. 🤓
+7
View File
@@ -0,0 +1,7 @@
# Тестирование базы данных { #testing-a-database }
Вы можете изучить базы данных, SQL и SQLModel в <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">документации SQLModel</a>. 🤓
Есть мини-<a href="https://sqlmodel.tiangolo.com/tutorial/fastapi/" class="external-link" target="_blank">руководство по использованию SQLModel с FastAPI</a>. ✨
В этом руководстве есть раздел о <a href="https://sqlmodel.tiangolo.com/tutorial/fastapi/tests/" class="external-link" target="_blank">тестировании SQL-баз данных</a>. 😎
+501
View File
@@ -0,0 +1,501 @@
# FastAPI { #fastapi }
<style>
.md-content .md-typeset h1 { display: none; }
</style>
<p align="center">
<a href="https://fastapi.tiangolo.com"><img src="https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" alt="FastAPI"></a>
</p>
<p align="center">
<em>Фреймворк FastAPI: высокая производительность, прост в изучении, быстрый в разработке, готов к продакшн</em>
</p>
<p align="center">
<a href="https://github.com/fastapi/fastapi/actions?query=workflow%3ATest+event%3Apush+branch%3Amaster" target="_blank">
<img src="https://github.com/fastapi/fastapi/actions/workflows/test.yml/badge.svg?event=push&branch=master" alt="Тест">
</a>
<a href="https://coverage-badge.samuelcolvin.workers.dev/redirect/fastapi/fastapi" target="_blank">
<img src="https://coverage-badge.samuelcolvin.workers.dev/fastapi/fastapi.svg" alt="Покрытие">
</a>
<a href="https://pypi.org/project/fastapi" target="_blank">
<img src="https://img.shields.io/pypi/v/fastapi?color=%2334D058&label=pypi%20package" alt="Версия пакета">
</a>
<a href="https://pypi.org/project/fastapi" target="_blank">
<img src="https://img.shields.io/pypi/pyversions/fastapi.svg?color=%2334D058" alt="Поддерживаемые версии Python">
</a>
</p>
---
**Документация**: <a href="https://fastapi.tiangolo.com/ru" target="_blank">https://fastapi.tiangolo.com</a>
**Исходный код**: <a href="https://github.com/fastapi/fastapi" target="_blank">https://github.com/fastapi/fastapi</a>
---
FastAPI — это современный, быстрый (высокопроизводительный) веб-фреймворк для создания API на Python, основанный на стандартных аннотациях типов Python.
Ключевые особенности:
* **Скорость**: Очень высокая производительность, на уровне **NodeJS** и **Go** (благодаря Starlette и Pydantic). [Один из самых быстрых доступных фреймворков Python](#performance).
* **Быстрота разработки**: Увеличьте скорость разработки фич примерно на 200–300%. *
* **Меньше ошибок**: Сократите примерно на 40% количество ошибок, вызванных человеком (разработчиком). *
* **Интуитивность**: Отличная поддержка редактора кода. <abbr title="также известное как: автодополнение, IntelliSense">Автозавершение</abbr> везде. Меньше времени на отладку.
* **Простота**: Разработан так, чтобы его было легко использовать и осваивать. Меньше времени на чтение документации.
* **Краткость**: Минимизируйте дублирование кода. Несколько возможностей из каждого объявления параметров. Меньше ошибок.
* **Надежность**: Получите код, готовый к продакшн. С автоматической интерактивной документацией.
* **На основе стандартов**: Основан на открытых стандартах API и полностью совместим с ними: <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> (ранее известный как Swagger) и <a href="https://json-schema.org/" class="external-link" target="_blank">JSON Schema</a>.
<small>* оценка на основе тестов внутренней команды разработчиков, создающих продакшн-приложения.</small>
## Спонсоры { #sponsors }
<!-- sponsors -->
{% if sponsors %}
{% for sponsor in sponsors.gold -%}
<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a>
{% endfor -%}
{%- for sponsor in sponsors.silver -%}
<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a>
{% endfor %}
{% endif %}
<!-- /sponsors -->
<a href="https://fastapi.tiangolo.com/ru/fastapi-people/#sponsors" class="external-link" target="_blank">Другие спонсоры</a>
## Мнения { #opinions }
"_[...] В последнее время я много где использую **FastAPI**. [...] На самом деле я планирую использовать его для всех **ML-сервисов моей команды в Microsoft**. Некоторые из них интегрируются в основной продукт **Windows**, а некоторые — в продукты **Office**._"
<div style="text-align: right; margin-right: 10%;">Kabir Khan - <strong>Microsoft</strong> <a href="https://github.com/fastapi/fastapi/pull/26" target="_blank"><small>(ref)</small></a></div>
---
"_Мы начали использовать библиотеку **FastAPI**, чтобы поднять **REST**-сервер, к которому можно обращаться за **предсказаниями**. [для Ludwig]_"
<div style="text-align: right; margin-right: 10%;">Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - <strong>Uber</strong> <a href="https://eng.uber.com/ludwig-v0-2/" target="_blank"><small>(ref)</small></a></div>
---
"_**Netflix** рада объявить об открытом релизе нашего фреймворка оркестрации **антикризисного управления**: **Dispatch**! [создан с помощью **FastAPI**]_"
<div style="text-align: right; margin-right: 10%;">Kevin Glisson, Marc Vilanova, Forest Monsen - <strong>Netflix</strong> <a href="https://netflixtechblog.com/introducing-dispatch-da4b8a2a8072" target="_blank"><small>(ref)</small></a></div>
---
"_Я в полном восторге от **FastAPI**. Это так весело!_"
<div style="text-align: right; margin-right: 10%;">Brian Okken - <strong>Ведущий подкаста <a href="https://pythonbytes.fm/episodes/show/123/time-to-right-the-py-wrongs?time_in_sec=855" target="_blank">Python Bytes</a></strong> <a href="https://x.com/brianokken/status/1112220079972728832" target="_blank"><small>(ref)</small></a></div>
---
"_Честно говоря, то, что вы создали, выглядит очень солидно и отполировано. Во многих смыслах это то, чем я хотел видеть **Hug** — очень вдохновляет видеть, как кто-то это создал._"
<div style="text-align: right; margin-right: 10%;">Timothy Crosley - <strong>Создатель <a href="https://github.com/hugapi/hug" target="_blank">Hug</a></strong> <a href="https://news.ycombinator.com/item?id=19455465" target="_blank"><small>(ref)</small></a></div>
---
"_Если вы хотите изучить один **современный фреймворк** для создания REST API, посмотрите **FastAPI** [...] Он быстрый, простой в использовании и лёгкий в изучении [...]_"
"_Мы переключились на **FastAPI** для наших **API** [...] Думаю, вам тоже понравится [...]_"
<div style="text-align: right; margin-right: 10%;">Ines Montani - Matthew Honnibal - <strong>Основатели <a href="https://explosion.ai" target="_blank">Explosion AI</a> — создатели <a href="https://spacy.io" target="_blank">spaCy</a></strong> <a href="https://x.com/_inesmontani/status/1144173225322143744" target="_blank"><small>(ref)</small></a> - <a href="https://x.com/honnibal/status/1144031421859655680" target="_blank"><small>(ref)</small></a></div>
---
"_Если кто-то собирается делать продакшн-API на Python, я настоятельно рекомендую **FastAPI**. Он **прекрасно спроектирован**, **прост в использовании** и **отлично масштабируется**, стал **ключевым компонентом** нашей стратегии API-first и приводит в действие множество автоматизаций и сервисов, таких как наш Virtual TAC Engineer._"
<div style="text-align: right; margin-right: 10%;">Deon Pillsbury - <strong>Cisco</strong> <a href="https://www.linkedin.com/posts/deonpillsbury_cisco-cx-python-activity-6963242628536487936-trAp/" target="_blank"><small>(ref)</small></a></div>
---
## **Typer**, FastAPI для CLI { #typer-the-fastapi-of-clis }
<a href="https://typer.tiangolo.com" target="_blank"><img src="https://typer.tiangolo.com/img/logo-margin/logo-margin-vector.svg" style="width: 20%;"></a>
Если вы создаёте приложение <abbr title="Command Line Interface – Интерфейс командной строки">CLI</abbr> для использования в терминале вместо веб-API, посмотрите <a href="https://typer.tiangolo.com/" class="external-link" target="_blank">**Typer**</a>.
**Typer** — младший брат FastAPI. И он задуман как **FastAPI для CLI**. ⌨️ 🚀
## Зависимости { #requirements }
FastAPI стоит на плечах гигантов:
* <a href="https://www.starlette.dev/" class="external-link" target="_blank">Starlette</a> для части, связанной с вебом.
* <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a> для части, связанной с данными.
## Установка { #installation }
Создайте и активируйте <a href="https://fastapi.tiangolo.com/ru/virtual-environments/" class="external-link" target="_blank">виртуальное окружение</a>, затем установите FastAPI:
<div class="termy">
```console
$ pip install "fastapi[standard]"
---> 100%
```
</div>
**Примечание**: Обязательно заключите `"fastapi[standard]"` в кавычки, чтобы это работало во всех терминалах.
## Пример { #example }
### Создание { #create-it }
Создайте файл `main.py` со следующим содержимым:
```Python
from typing import Union
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
@app.get("/items/{item_id}")
def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}
```
<details markdown="1">
<summary>Или используйте <code>async def</code>...</summary>
Если ваш код использует `async` / `await`, используйте `async def`:
```Python hl_lines="9 14"
from typing import Union
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def read_root():
return {"Hello": "World"}
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}
```
**Примечание**:
Если не уверены, посмотрите раздел _«Нет времени?»_ о <a href="https://fastapi.tiangolo.com/ru/async/#in-a-hurry" target="_blank">`async` и `await` в документации</a>.
</details>
### Запуск { #run-it }
Запустите сервер командой:
<div class="termy">
```console
$ fastapi dev main.py
╭────────── FastAPI CLI - Development mode ───────────╮
│ │
│ Serving at: http://127.0.0.1:8000 │
│ │
│ API docs: http://127.0.0.1:8000/docs │
│ │
│ Running in development mode, for production use: │
│ │
│ fastapi run │
│ │
╰─────────────────────────────────────────────────────╯
INFO: Will watch for changes in these directories: ['/home/user/code/awesomeapp']
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [2248755] using WatchFiles
INFO: Started server process [2248757]
INFO: Waiting for application startup.
INFO: Application startup complete.
```
</div>
<details markdown="1">
<summary>О команде <code>fastapi dev main.py</code>...</summary>
Команда `fastapi dev` читает ваш файл `main.py`, находит в нём приложение **FastAPI** и запускает сервер с помощью <a href="https://www.uvicorn.dev" class="external-link" target="_blank">Uvicorn</a>.
По умолчанию `fastapi dev` запускается с включённой авто-перезагрузкой для локальной разработки.
Подробнее в <a href="https://fastapi.tiangolo.com/ru/fastapi-cli/" target="_blank">документации по FastAPI CLI</a>.
</details>
### Проверка { #check-it }
Откройте браузер на <a href="http://127.0.0.1:8000/items/5?q=somequery" class="external-link" target="_blank">http://127.0.0.1:8000/items/5?q=somequery</a>.
Вы увидите JSON-ответ:
```JSON
{"item_id": 5, "q": "somequery"}
```
Вы уже создали API, который:
* Получает HTTP-запросы по _путям_ `/` и `/items/{item_id}`.
* Оба _пути_ используют `GET` <em>операции</em> (также известные как HTTP _методы_).
* _Путь_ `/items/{item_id}` имеет _параметр пути_ `item_id`, который должен быть `int`.
* _Путь_ `/items/{item_id}` имеет необязательный `str` _параметр запроса_ `q`.
### Интерактивная документация 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>.
Вы увидите автоматическую интерактивную документацию API (предоставлена <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>):
![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png)
### Альтернативная документация API { #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="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>):
![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png)
## Пример обновления { #example-upgrade }
Теперь измените файл `main.py`, чтобы принимать тело запроса из `PUT` запроса.
Объявите тело, используя стандартные типы Python, спасибо Pydantic.
```Python hl_lines="4 9-12 25-27"
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
is_offer: Union[bool, None] = None
@app.get("/")
def read_root():
return {"Hello": "World"}
@app.get("/items/{item_id}")
def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}
@app.put("/items/{item_id}")
def update_item(item_id: int, item: Item):
return {"item_name": item.name, "item_id": item_id}
```
Сервер `fastapi dev` должен перезагрузиться автоматически.
### Обновление интерактивной документации API { #interactive-api-docs-upgrade }
Перейдите на <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
* Интерактивная документация API будет автоматически обновлена, включая новое тело:
![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png)
* Нажмите кнопку «Try it out», это позволит вам заполнить параметры и напрямую взаимодействовать с API:
![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png)
* Затем нажмите кнопку «Execute», интерфейс свяжется с вашим API, отправит параметры, получит результаты и отобразит их на экране:
![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png)
### Обновление альтернативной документации API { #alternative-api-docs-upgrade }
Теперь откройте <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>.
* Альтернативная документация также отразит новый параметр запроса и тело:
![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png)
### Подведём итоги { #recap }
Итак, вы объявляете **один раз** типы параметров, тела запроса и т.д. как параметры функции.
Вы делаете это с помощью стандартных современных типов Python.
Вам не нужно изучать новый синтаксис, методы или классы конкретной библиотеки и т.п.
Только стандартный **Python**.
Например, для `int`:
```Python
item_id: int
```
или для более сложной модели `Item`:
```Python
item: Item
```
...и с этим единственным объявлением вы получаете:
* Поддержку редактора кода, включая:
* Автозавершение.
* Проверку типов.
* Валидацию данных:
* Автоматические и понятные ошибки, когда данные некорректны.
* Валидацию даже для глубоко вложенных объектов JSON.
* <abbr title="также известное как: сериализация, парсинг, маршалинг">Преобразование</abbr> входных данных: из сети в данные и типы Python. Чтение из:
* JSON.
* Параметров пути.
* Параметров запроса.
* Cookies.
* HTTP-заголовков.
* Форм.
* Файлов.
* <abbr title="также известное как: сериализация, парсинг, маршалинг">Преобразование</abbr> выходных данных: из данных и типов Python в данные сети (например, JSON):
* Преобразование типов Python (`str`, `int`, `float`, `bool`, `list` и т.д.).
* Объекты `datetime`.
* Объекты `UUID`.
* Модели баз данных.
* ...и многое другое.
* Автоматическую интерактивную документацию API, включая 2 альтернативных интерфейса:
* Swagger UI.
* ReDoc.
---
Возвращаясь к предыдущему примеру кода, **FastAPI** будет:
* Валидировать наличие `item_id` в пути для `GET` и `PUT` запросов.
* Валидировать, что `item_id` имеет тип `int` для `GET` и `PUT` запросов.
* Если это не так, клиент увидит полезную понятную ошибку.
* Проверять, есть ли необязательный параметр запроса с именем `q` (например, `http://127.0.0.1:8000/items/foo?q=somequery`) для `GET` запросов.
* Поскольку параметр `q` объявлен с `= None`, он необязателен.
* Без `None` он был бы обязательным (как тело запроса в случае с `PUT`).
* Для `PUT` запросов к `/items/{item_id}` читать тело запроса как JSON:
* Проверять, что есть обязательный атрибут `name`, который должен быть `str`.
* Проверять, что есть обязательный атрибут `price`, который должен быть `float`.
* Проверять, что есть необязательный атрибут `is_offer`, который должен быть `bool`, если он присутствует.
* Всё это также будет работать для глубоко вложенных объектов JSON.
* Автоматически преобразовывать из и в JSON.
* Документировать всё с помощью OpenAPI, что может быть использовано:
* Системами интерактивной документации.
* Системами автоматической генерации клиентского кода для многих языков.
* Предоставлять 2 веб-интерфейса интерактивной документации напрямую.
---
Мы только поверхностно ознакомились, но вы уже понимаете, как всё это работает.
Попробуйте изменить строку:
```Python
return {"item_name": item.name, "item_id": item_id}
```
...из:
```Python
... "item_name": item.name ...
```
...на:
```Python
... "item_price": item.price ...
```
...и посмотрите, как ваш редактор кода будет автоматически дополнять атрибуты и знать их типы:
![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png)
Более полный пример с дополнительными возможностями см. в <a href="https://fastapi.tiangolo.com/ru/tutorial/">Учебник - Руководство пользователя</a>.
**Осторожно, спойлер**: учебник - руководство включает:
* Объявление **параметров** из других источников: **HTTP-заголовки**, **cookies**, **поля формы** и **файлы**.
* Как задать **ограничения валидации** вроде `maximum_length` или `regex`.
* Очень мощную и простую в использовании систему **<abbr title="также известная как: компоненты, ресурсы, провайдеры, сервисы, инъекции">внедрения зависимостей</abbr>**.
* Безопасность и аутентификацию, включая поддержку **OAuth2** с **JWT токенами** и **HTTP Basic** аутентификацию.
* Более продвинутые (но столь же простые) приёмы объявления **глубоко вложенных JSON-моделей** (спасибо Pydantic).
* Интеграцию **GraphQL** с <a href="https://strawberry.rocks" class="external-link" target="_blank">Strawberry</a> и другими библиотеками.
* Множество дополнительных функций (благодаря Starlette), таких как:
* **WebSockets**
* чрезвычайно простые тесты на основе HTTPX и `pytest`
* **CORS**
* **сессии с использованием cookie**
* ...и многое другое.
## Производительность { #performance }
Независимые бенчмарки TechEmpower показывают приложения **FastAPI**, работающие под управлением Uvicorn, как <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" class="external-link" target="_blank">один из самых быстрых доступных фреймворков Python</a>, уступающий только самим Starlette и Uvicorn (используются внутри FastAPI). (*)
Чтобы узнать больше, см. раздел <a href="https://fastapi.tiangolo.com/ru/benchmarks/" class="internal-link" target="_blank">Бенчмарки</a>.
## Зависимости { #dependencies }
FastAPI зависит от Pydantic и Starlette.
### Зависимости `standard` { #standard-dependencies }
Когда вы устанавливаете FastAPI с помощью `pip install "fastapi[standard]"`, он идёт с группой опциональных зависимостей `standard`:
Используется Pydantic:
* <a href="https://github.com/JoshData/python-email-validator" target="_blank"><code>email-validator</code></a> — для проверки адресов электронной почты.
Используется Starlette:
* <a href="https://www.python-httpx.org" target="_blank"><code>httpx</code></a> — обязателен, если вы хотите использовать `TestClient`.
* <a href="https://jinja.palletsprojects.com" target="_blank"><code>jinja2</code></a> — обязателен, если вы хотите использовать конфигурацию шаблонов по умолчанию.
* <a href="https://github.com/Kludex/python-multipart" target="_blank"><code>python-multipart</code></a> — обязателен, если вы хотите поддерживать <abbr title="преобразование строки, полученной из HTTP-запроса, в данные Python">«парсинг»</abbr> форм через `request.form()`.
Используется FastAPI:
* <a href="https://www.uvicorn.dev" target="_blank"><code>uvicorn</code></a> — сервер, который загружает и обслуживает ваше приложение. Включает `uvicorn[standard]`, содержащий некоторые зависимости (например, `uvloop`), нужные для высокой производительности.
* `fastapi-cli[standard]` — чтобы предоставить команду `fastapi`.
* Включает `fastapi-cloud-cli`, который позволяет развернуть ваше приложение FastAPI в <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>.
### Без зависимостей `standard` { #without-standard-dependencies }
Если вы не хотите включать опциональные зависимости `standard`, можно установить `pip install fastapi` вместо `pip install "fastapi[standard]"`.
### Без `fastapi-cloud-cli` { #without-fastapi-cloud-cli }
Если вы хотите установить FastAPI со стандартными зависимостями, но без `fastapi-cloud-cli`, установите `pip install "fastapi[standard-no-fastapi-cloud-cli]"`.
### Дополнительные опциональные зависимости { #additional-optional-dependencies }
Есть дополнительные зависимости, которые вы можете установить.
Дополнительные опциональные зависимости Pydantic:
* <a href="https://docs.pydantic.dev/latest/usage/pydantic_settings/" target="_blank"><code>pydantic-settings</code></a> — для управления настройками.
* <a href="https://docs.pydantic.dev/latest/usage/types/extra_types/extra_types/" target="_blank"><code>pydantic-extra-types</code></a> — дополнительные типы для использования с Pydantic.
Дополнительные опциональные зависимости FastAPI:
* <a href="https://github.com/ijl/orjson" target="_blank"><code>orjson</code></a> — обязателен, если вы хотите использовать `ORJSONResponse`.
* <a href="https://github.com/esnme/ultrajson" target="_blank"><code>ujson</code></a> — обязателен, если вы хотите использовать `UJSONResponse`.
## Лицензия { #license }
Этот проект распространяется на условиях лицензии MIT.
+5
View File
@@ -0,0 +1,5 @@
# Обучение { #learn }
Здесь представлены вводные разделы и учебные пособия для изучения **FastAPI**.
Вы можете считать это **книгой**, **курсом**, **официальным** и рекомендуемым способом изучения FastAPI. 😎
+28
View File
@@ -0,0 +1,28 @@
# Шаблон Full Stack FastAPI { #full-stack-fastapi-template }
Шаблоны, хотя обычно поставляются с определённой конфигурацией, спроектированы так, чтобы быть гибкими и настраиваемыми. Это позволяет вам изменять их и адаптировать под требования вашего проекта, что делает их отличной отправной точкой. 🏁
Вы можете использовать этот шаблон для старта: в нём уже сделана значительная часть начальной настройки, безопасность, база данных и несколько эндпоинтов API.
Репозиторий GitHub: <a href="https://github.com/tiangolo/full-stack-fastapi-template" class="external-link" target="_blank">Full Stack FastAPI Template</a>
## Шаблон Full Stack FastAPI — Технологический стек и возможности { #full-stack-fastapi-template-technology-stack-and-features }
- ⚡ [**FastAPI**](https://fastapi.tiangolo.com/ru) для бэкенд‑API на Python.
- 🧰 [SQLModel](https://sqlmodel.tiangolo.com) для взаимодействия с SQL‑базой данных на Python (ORM).
- 🔍 [Pydantic](https://docs.pydantic.dev), используется FastAPI, для валидации данных и управления настройками.
- 💾 [PostgreSQL](https://www.postgresql.org) в качестве SQL‑базы данных.
- 🚀 [React](https://react.dev) для фронтенда.
- 💃 Используются TypeScript, хуки, [Vite](https://vitejs.dev) и другие части современного фронтенд‑стека.
- 🎨 [Chakra UI](https://chakra-ui.com) для компонентов фронтенда.
- 🤖 Автоматически сгенерированный фронтенд‑клиент.
- 🧪 [Playwright](https://playwright.dev) для End‑to‑End тестирования.
- 🦇 Поддержка тёмной темы.
- 🐋 [Docker Compose](https://www.docker.com) для разработки и продакшна.
- 🔒 Безопасное хэширование паролей по умолчанию.
- 🔑 Аутентификация по JWT‑токенам.
- 📫 Восстановление пароля по электронной почте.
- ✅ Тесты с [Pytest](https://pytest.org).
- 📞 [Traefik](https://traefik.io) в роли обратного прокси / балансировщика нагрузки.
- 🚢 Инструкции по развёртыванию с использованием Docker Compose, включая настройку фронтенд‑прокси Traefik для автоматического получения сертификатов HTTPS.
- 🏭 CI (continuous integration) и CD (continuous deployment) на основе GitHub Actions.
+576
View File
@@ -0,0 +1,576 @@
# Введение в типы Python { #python-types-intro }
Python поддерживает необязательные «подсказки типов» (их также называют «аннотациями типов»).
Эти **«подсказки типов»** или аннотации — это специальный синтаксис, позволяющий объявлять <abbr title="например: str, int, float, bool">тип</abbr> переменной.
Объявляя типы для ваших переменных, редакторы кода и инструменты смогут лучше вас поддерживать.
Это всего лишь **краткое руководство / напоминание** о подсказках типов в Python. Оно охватывает только минимум, необходимый для их использования с **FastAPI**... что на самом деле очень мало.
**FastAPI** целиком основан на этих подсказках типов — они дают ему множество преимуществ и выгод.
Но даже если вы никогда не используете **FastAPI**, вам будет полезно немного узнать о них.
/// note | Примечание
Если вы являетесь экспертом в Python и уже знаете всё о подсказках типов, переходите к следующей главе.
///
## Мотивация { #motivation }
Давайте начнем с простого примера:
{* ../../docs_src/python_types/tutorial001.py *}
Вызов этой программы выводит:
```
John Doe
```
Функция делает следующее:
* Принимает `first_name` и `last_name`.
* Преобразует первую букву каждого значения в верхний регистр с помощью `title()`.
* <abbr title="Объединяет в одно целое. Содержимое одного — сразу после другого.">Соединяет</abbr> их пробелом посередине.
{* ../../docs_src/python_types/tutorial001.py hl[2] *}
### Отредактируем пример { #edit-it }
Это очень простая программа.
А теперь представьте, что вы пишете её с нуля.
В какой-то момент вы бы начали определение функции, у вас были бы готовы параметры...
Но затем нужно вызвать «тот метод, который делает первую букву заглавной».
Это был `upper`? Или `uppercase`? `first_uppercase`? `capitalize`?
Тогда вы пробуете старого друга программиста — автозавершение редактора кода.
Вы вводите первый параметр функции, `first_name`, затем точку (`.`) и нажимаете `Ctrl+Space`, чтобы вызвать автозавершение.
Но, к сожалению, ничего полезного не находится:
<img src="/img/python-types/image01.png">
### Добавим типы { #add-types }
Давайте изменим одну строку из предыдущей версии.
Мы поменяем ровно этот фрагмент — параметры функции — с:
```Python
first_name, last_name
```
на:
```Python
first_name: str, last_name: str
```
Вот и всё.
Это и есть «подсказки типов»:
{* ../../docs_src/python_types/tutorial002.py hl[1] *}
Это не то же самое, что объявление значений по умолчанию, как, например:
```Python
first_name="john", last_name="doe"
```
Это другая вещь.
Здесь мы используем двоеточия (`:`), а не знак равенства (`=`).
И добавление подсказок типов обычно не меняет поведение программы по сравнению с вариантом без них.
Но теперь представьте, что вы снова посередине написания этой функции, только уже с подсказками типов.
В тот же момент вы пробуете вызвать автозавершение с помощью `Ctrl+Space` — и видите:
<img src="/img/python-types/image02.png">
С этим вы можете прокручивать варианты, пока не найдёте тот самый:
<img src="/img/python-types/image03.png">
## Больше мотивации { #more-motivation }
Посмотрите на эту функцию — у неё уже есть подсказки типов:
{* ../../docs_src/python_types/tutorial003.py hl[1] *}
Так как редактор кода знает типы переменных, вы получаете не только автозавершение, но и проверки ошибок:
<img src="/img/python-types/image04.png">
Теперь вы знаете, что нужно исправить — преобразовать `age` в строку с помощью `str(age)`:
{* ../../docs_src/python_types/tutorial004.py hl[2] *}
## Объявление типов { #declaring-types }
Вы только что увидели основное место, где объявляют подсказки типов — параметры функции.
Это также основное место, где вы будете использовать их с **FastAPI**.
### Простые типы { #simple-types }
Вы можете объявлять все стандартные типы Python, не только `str`.
Можно использовать, например:
* `int`
* `float`
* `bool`
* `bytes`
{* ../../docs_src/python_types/tutorial005.py hl[1] *}
### Generic-типы с параметрами типов { #generic-types-with-type-parameters }
Есть структуры данных, которые могут содержать другие значения, например, `dict`, `list`, `set` и `tuple`. И внутренние значения тоже могут иметь свой тип.
Такие типы, которые содержат внутренние типы, называют «**generic**»-типами. И их можно объявлять, в том числе с указанием внутренних типов.
Чтобы объявлять эти типы и их внутренние типы, вы можете использовать стандартный модуль Python `typing`. Он существует специально для поддержки подсказок типов.
#### Новые версии Python { #newer-versions-of-python }
Синтаксис с использованием `typing` **совместим** со всеми версиями, от Python 3.6 до самых новых, включая Python 3.9, Python 3.10 и т.д.
По мере развития Python **новые версии** получают улучшенную поддержку этих аннотаций типов, и во многих случаях вам даже не нужно импортировать и использовать модуль `typing`, чтобы объявлять аннотации типов.
Если вы можете выбрать более свежую версию Python для проекта, вы получите дополнительную простоту.
Во всей документации есть примеры, совместимые с каждой версией Python (когда есть различия).
Например, «**Python 3.6+**» означает совместимость с Python 3.6 и выше (включая 3.7, 3.8, 3.9, 3.10 и т.д.). А «**Python 3.9+**» — совместимость с Python 3.9 и выше (включая 3.10 и т.п.).
Если вы можете использовать **последние версии Python**, используйте примеры для самой новой версии — у них будет **самый лучший и простой синтаксис**, например, «**Python 3.10+**».
#### List { #list }
Например, давайте определим переменную как `list` из `str`.
//// tab | Python 3.9+
Объявите переменную с тем же синтаксисом двоеточия (`:`).
В качестве типа укажите `list`.
Так как список — это тип, содержащий внутренние типы, укажите их в квадратных скобках:
```Python hl_lines="1"
{!> ../../docs_src/python_types/tutorial006_py39.py!}
```
////
//// tab | Python 3.8+
Из `typing` импортируйте `List` (с заглавной `L`):
```Python hl_lines="1"
{!> ../../docs_src/python_types/tutorial006.py!}
```
Объявите переменную с тем же синтаксисом двоеточия (`:`).
В качестве типа используйте `List`, который вы импортировали из `typing`.
Так как список — это тип, содержащий внутренние типы, укажите их в квадратных скобках:
```Python hl_lines="4"
{!> ../../docs_src/python_types/tutorial006.py!}
```
////
/// info | Информация
Эти внутренние типы в квадратных скобках называются «параметрами типов».
В данном случае `str` — это параметр типа, передаваемый в `List` (или `list` в Python 3.9 и выше).
///
Это означает: «переменная `items` — это `list`, и каждый элемент этого списка — `str`».
/// tip | Совет
Если вы используете Python 3.9 или выше, вам не нужно импортировать `List` из `typing`, можно использовать обычный встроенный тип `list`.
///
Таким образом, ваш редактор кода сможет помогать даже при обработке элементов списка:
<img src="/img/python-types/image05.png">
Без типов добиться этого почти невозможно.
Обратите внимание, что переменная `item` — один из элементов списка `items`.
И всё же редактор кода знает, что это `str`, и даёт соответствующую поддержку.
#### Tuple и Set { #tuple-and-set }
Аналогично вы бы объявили `tuple` и `set`:
//// tab | Python 3.9+
```Python hl_lines="1"
{!> ../../docs_src/python_types/tutorial007_py39.py!}
```
////
//// tab | Python 3.8+
```Python hl_lines="1 4"
{!> ../../docs_src/python_types/tutorial007.py!}
```
////
Это означает:
* Переменная `items_t` — это `tuple` из 3 элементов: `int`, ещё один `int` и `str`.
* Переменная `items_s` — это `set`, и каждый элемент имеет тип `bytes`.
#### Dict { #dict }
Чтобы определить `dict`, вы передаёте 2 параметра типов, разделённые запятой.
Первый параметр типа — для ключей `dict`.
Второй параметр типа — для значений `dict`:
//// tab | Python 3.9+
```Python hl_lines="1"
{!> ../../docs_src/python_types/tutorial008_py39.py!}
```
////
//// tab | Python 3.8+
```Python hl_lines="1 4"
{!> ../../docs_src/python_types/tutorial008.py!}
```
////
Это означает:
* Переменная `prices` — это `dict`:
* Ключи этого `dict` имеют тип `str` (скажем, название каждой позиции).
* Значения этого `dict` имеют тип `float` (скажем, цена каждой позиции).
#### Union { #union }
Вы можете объявить, что переменная может быть **одним из нескольких типов**, например, `int` или `str`.
В Python 3.6 и выше (включая Python 3.10) вы можете использовать тип `Union` из `typing` и перечислить в квадратных скобках все допустимые типы.
В Python 3.10 также появился **новый синтаксис**, где допустимые типы можно указать через <abbr title='также называется «побитовый оператор OR», но это значение здесь нерелевантно'>вертикальную черту (`|`)</abbr>.
//// tab | Python 3.10+
```Python hl_lines="1"
{!> ../../docs_src/python_types/tutorial008b_py310.py!}
```
////
//// tab | Python 3.8+
```Python hl_lines="1 4"
{!> ../../docs_src/python_types/tutorial008b.py!}
```
////
В обоих случаях это означает, что `item` может быть `int` или `str`.
#### Возможно `None` { #possibly-none }
Вы можете объявить, что значение может иметь определённый тип, например `str`, но также может быть и `None`.
В Python 3.6 и выше (включая Python 3.10) это можно объявить, импортировав и используя `Optional` из модуля `typing`.
```Python hl_lines="1 4"
{!../../docs_src/python_types/tutorial009.py!}
```
Использование `Optional[str]` вместо просто `str` позволит редактору кода помочь вам обнаружить ошибки, когда вы предполагаете, что значение всегда `str`, хотя на самом деле оно может быть и `None`.
`Optional[Something]` — это на самом деле сокращение для `Union[Something, None]`, они эквивалентны.
Это также означает, что в Python 3.10 вы можете использовать `Something | None`:
//// tab | Python 3.10+
```Python hl_lines="1"
{!> ../../docs_src/python_types/tutorial009_py310.py!}
```
////
//// tab | Python 3.8+
```Python hl_lines="1 4"
{!> ../../docs_src/python_types/tutorial009.py!}
```
////
//// tab | Python 3.8+ альтернативный вариант
```Python hl_lines="1 4"
{!> ../../docs_src/python_types/tutorial009b.py!}
```
////
#### Использовать `Union` или `Optional` { #using-union-or-optional }
Если вы используете версию Python ниже 3.10, вот совет с моей весьма **субъективной** точки зрения:
* 🚨 Избегайте использования `Optional[SomeType]`
* Вместо этого ✨ **используйте `Union[SomeType, None]`** ✨.
Оба варианта эквивалентны и внутри одинаковы, но я бы рекомендовал `Union` вместо `Optional`, потому что слово «**optional**» («необязательный») может навести на мысль, что значение необязательное, хотя на самом деле оно означает «может быть `None`», даже если параметр не является необязательным и всё ещё обязателен.
Мне кажется, `Union[SomeType, None]` более явно выражает смысл.
Речь только о словах и названиях. Но эти слова могут влиять на то, как вы и ваши коллеги думаете о коде.
В качестве примера возьмём эту функцию:
{* ../../docs_src/python_types/tutorial009c.py hl[1,4] *}
Параметр `name` определён как `Optional[str]`, но он **не необязательный** — вы не можете вызвать функцию без этого параметра:
```Python
say_hi() # О нет, это вызывает ошибку! 😱
```
Параметр `name` всё ещё **обязателен** (не *optional*), потому что у него нет значения по умолчанию. При этом `name` принимает `None` как значение:
```Python
say_hi(name=None) # Это работает, None допустим 🎉
```
Хорошая новость: как только вы перейдёте на Python 3.10, об этом можно не переживать — вы сможете просто использовать `|` для объединения типов:
{* ../../docs_src/python_types/tutorial009c_py310.py hl[1,4] *}
И тогда вам не придётся задумываться о названиях вроде `Optional` и `Union`. 😎
#### Generic-типы { #generic-types }
Типы, которые принимают параметры типов в квадратных скобках, называются **Generic-типами** или **Generics**, например:
//// tab | Python 3.10+
Вы можете использовать те же встроенные типы как generics (с квадратными скобками и типами внутри):
* `list`
* `tuple`
* `set`
* `dict`
И, как и в Python 3.8, из модуля `typing`:
* `Union`
* `Optional` (так же, как в Python 3.8)
* ...и другие.
В Python 3.10, как альтернативу generics `Union` и `Optional`, можно использовать <abbr title='также называется «побитовый оператор OR», но это значение здесь нерелевантно'>вертикальную черту (`|`)</abbr> для объявления объединений типов — это гораздо лучше и проще.
////
//// tab | Python 3.9+
Вы можете использовать те же встроенные типы как generics (с квадратными скобками и типами внутри):
* `list`
* `tuple`
* `set`
* `dict`
И, как и в Python 3.8, из модуля `typing`:
* `Union`
* `Optional`
* ...и другие.
////
//// tab | Python 3.8+
* `List`
* `Tuple`
* `Set`
* `Dict`
* `Union`
* `Optional`
* ...и другие.
////
### Классы как типы { #classes-as-types }
Вы также можете объявлять класс как тип переменной.
Допустим, у вас есть класс `Person` с именем:
{* ../../docs_src/python_types/tutorial010.py hl[1:3] *}
Тогда вы можете объявить переменную типа `Person`:
{* ../../docs_src/python_types/tutorial010.py hl[6] *}
И снова вы получите полную поддержку редактора кода:
<img src="/img/python-types/image06.png">
Обратите внимание, что это означает: «`one_person` — это **экземпляр** класса `Person`».
Это не означает: «`one_person` — это **класс** с именем `Person`».
## Pydantic-модели { #pydantic-models }
<a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a> — это библиотека Python для валидации данных.
Вы объявляете «форму» данных как классы с атрибутами.
И у каждого атрибута есть тип.
Затем вы создаёте экземпляр этого класса с некоторыми значениями — он провалидирует значения, преобразует их к соответствующему типу (если это применимо) и вернёт вам объект со всеми данными.
И вы получите полную поддержку редактора кода для этого результирующего объекта.
Пример из официальной документации Pydantic:
//// tab | Python 3.10+
```Python
{!> ../../docs_src/python_types/tutorial011_py310.py!}
```
////
//// tab | Python 3.9+
```Python
{!> ../../docs_src/python_types/tutorial011_py39.py!}
```
////
//// tab | Python 3.8+
```Python
{!> ../../docs_src/python_types/tutorial011.py!}
```
////
/// info | Информация
Чтобы узнать больше о <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic, ознакомьтесь с его документацией</a>.
///
**FastAPI** целиком основан на Pydantic.
Вы увидите намного больше всего этого на практике в [Руководстве пользователя](tutorial/index.md){.internal-link target=_blank}.
/// tip | Совет
У Pydantic есть особое поведение, когда вы используете `Optional` или `Union[Something, None]` без значения по умолчанию. Подробнее читайте в документации Pydantic: <a href="https://docs.pydantic.dev/2.3/usage/models/#required-fields" class="external-link" target="_blank">Required Optional fields</a>.
///
## Подсказки типов с аннотациями метаданных { #type-hints-with-metadata-annotations }
В Python также есть возможность добавлять **дополнительные <abbr title="Данные о данных, в данном случае — информация о типе, например описание.">метаданные</abbr>** к подсказкам типов с помощью `Annotated`.
//// tab | Python 3.9+
В Python 3.9 `Annotated` входит в стандартную библиотеку, поэтому вы можете импортировать его из `typing`.
```Python hl_lines="1 4"
{!> ../../docs_src/python_types/tutorial013_py39.py!}
```
////
//// tab | Python 3.8+
В версиях ниже Python 3.9 импортируйте `Annotated` из `typing_extensions`.
Он уже будет установлен вместе с **FastAPI**.
```Python hl_lines="1 4"
{!> ../../docs_src/python_types/tutorial013.py!}
```
////
Сам Python ничего не делает с `Annotated`. А для редакторов кода и других инструментов тип по-прежнему `str`.
Но вы можете использовать это место в `Annotated`, чтобы передать **FastAPI** дополнительные метаданные о том, как вы хотите, чтобы ваше приложение себя вело.
Важно помнить, что **первый параметр типа**, который вы передаёте в `Annotated`, — это **фактический тип**. Всё остальное — просто метаданные для других инструментов.
Пока вам достаточно знать, что `Annotated` существует и это — стандартный Python. 😎
Позже вы увидите, насколько это **мощно**.
/// tip | Совет
Тот факт, что это **стандартный Python**, означает, что вы по-прежнему получите **лучший возможный разработческий опыт** в вашем редакторе кода, с инструментами для анализа и рефакторинга кода и т.д. ✨
А ещё ваш код будет очень совместим со множеством других инструментов и библиотек Python. 🚀
///
## Аннотации типов в **FastAPI** { #type-hints-in-fastapi }
**FastAPI** использует эти подсказки типов для выполнения нескольких задач.
С **FastAPI** вы объявляете параметры с подсказками типов и получаете:
* **Поддержку редактора кода**.
* **Проверки типов**.
...и **FastAPI** использует эти же объявления для:
* **Определения требований**: из path-параметров, query-параметров, HTTP-заголовков, тел запросов, зависимостей и т.д.
* **Преобразования данных**: из HTTP-запроса к требуемому типу.
* **Валидации данных**: приходящих с каждого HTTP-запроса:
* Генерации **автоматических ошибок**, возвращаемых клиенту, когда данные некорректны.
* **Документирования** API с использованием OpenAPI:
* что затем используется пользовательскими интерфейсами автоматической интерактивной документации.
Всё это может звучать абстрактно. Не волнуйтесь. Вы увидите всё это в действии в [Руководстве пользователя](tutorial/index.md){.internal-link target=_blank}.
Важно то, что, используя стандартные типы Python в одном месте (вместо добавления дополнительных классов, декораторов и т.д.), **FastAPI** сделает за вас большую часть работы.
/// info | Информация
Если вы уже прошли всё руководство и вернулись, чтобы узнать больше о типах, хорошим ресурсом будет <a href="https://mypy.readthedocs.io/en/latest/cheat_sheet_py3.html" class="external-link" target="_blank">«шпаргалка» от `mypy`</a>.
///
+3
View File
@@ -0,0 +1,3 @@
# Ресурсы { #resources }
Дополнительные ресурсы, внешние ссылки, статьи и многое другое. ✈️
+84
View File
@@ -0,0 +1,84 @@
# Фоновые задачи { #background-tasks }
Вы можете создавать фоновые задачи, которые будут выполняться после возврата ответа.
Это полезно для операций, которые должны произойти после HTTP-запроса, но клиенту не обязательно ждать их завершения, чтобы получить ответ.
Например:
* Уведомления по электронной почте, отправляемые после выполнения действия:
* Так как подключение к почтовому серверу и отправка письма обычно «медленные» (несколько секунд), вы можете сразу вернуть ответ, а отправку уведомления выполнить в фоне.
* Обработка данных:
* Например, если вы получаете файл, который должен пройти через медленный процесс, вы можете вернуть ответ «Accepted» (HTTP 202) и обработать файл в фоне.
## Использование `BackgroundTasks` { #using-backgroundtasks }
Сначала импортируйте `BackgroundTasks` и объявите параметр в вашей функции‑обработчике пути с типом `BackgroundTasks`:
{* ../../docs_src/background_tasks/tutorial001.py hl[1,13] *}
**FastAPI** создаст объект типа `BackgroundTasks` для вас и передаст его через этот параметр.
## Создание функции для фоновой задачи { #create-a-task-function }
Создайте функцию, которую нужно запустить как фоновую задачу.
Это обычная функция, которая может принимать параметры.
Это может быть как `async def`, так и обычная функция `def`, **FastAPI** знает, как корректно её выполнить.
В этом случае функция задачи будет записывать данные в файл (имитируя отправку письма).
Так как операция записи не использует `async` и `await`, мы определим функцию как обычную `def`:
{* ../../docs_src/background_tasks/tutorial001.py hl[6:9] *}
## Добавление фоновой задачи { #add-the-background-task }
Внутри вашей функции‑обработчика пути передайте функцию задачи объекту фоновых задач методом `.add_task()`:
{* ../../docs_src/background_tasks/tutorial001.py hl[14] *}
`.add_task()` принимает следующие аргументы:
* Функцию задачи, которую нужно выполнить в фоне (`write_notification`).
* Последовательность позиционных аргументов, которые должны быть переданы функции задачи, в порядке (`email`).
* Любые именованные аргументы, которые должны быть переданы функции задачи (`message="some notification"`).
## Встраивание зависимостей { #dependency-injection }
Использование `BackgroundTasks` также работает с системой встраивания зависимостей, вы можете объявить параметр типа `BackgroundTasks` на нескольких уровнях: в функции‑обработчике пути, в зависимости (dependable), в подзависимости и т. д.
**FastAPI** знает, что делать в каждом случае и как переиспользовать один и тот же объект, так чтобы все фоновые задачи были объединены и затем выполнены в фоне:
{* ../../docs_src/background_tasks/tutorial002_an_py310.py hl[13,15,22,25] *}
В этом примере сообщения будут записаны в файл `log.txt` после отправки ответа.
Если в запросе была строка запроса (query), она будет записана в лог фоновой задачей.
Затем другая фоновая задача, созданная в функции‑обработчике пути, запишет сообщение, используя path‑параметр `email`.
## Технические детали { #technical-details }
Класс `BackgroundTasks` приходит напрямую из <a href="https://www.starlette.dev/background/" class="external-link" target="_blank">`starlette.background`</a>.
Он импортируется/включается прямо в FastAPI, чтобы вы могли импортировать его из `fastapi` и избежать случайного импорта альтернативного `BackgroundTask` (без `s` на конце) из `starlette.background`.
Используя только `BackgroundTasks` (а не `BackgroundTask`), его можно применять как параметр функции‑обработчика пути, и **FastAPI** сделает остальное за вас, как при использовании объекта `Request` напрямую.
По‑прежнему можно использовать один `BackgroundTask` в FastAPI, но тогда вам нужно создать объект в своём коде и вернуть Starlette `Response`, включающий его.
Подробнее см. в <a href="https://www.starlette.dev/background/" class="external-link" target="_blank">официальной документации Starlette по фоновым задачам</a>.
## Предостережение { #caveat }
Если вам нужно выполнять тяжелые вычисления в фоне, и при этом они не обязательно должны запускаться тем же процессом (например, вам не нужно делиться памятью, переменными и т. п.), вам могут подойти более мощные инструменты, такие как <a href="https://docs.celeryq.dev" class="external-link" target="_blank">Celery</a>.
Они обычно требуют более сложной конфигурации, менеджера очереди сообщений/заданий (например, RabbitMQ или Redis), но позволяют запускать фоновые задачи в нескольких процессах и, что особенно важно, на нескольких серверах.
Но если вам нужен доступ к переменным и объектам из того же приложения **FastAPI**, или нужно выполнять небольшие фоновые задачи (например, отправку email‑уведомления), вы можете просто использовать `BackgroundTasks`.
## Резюме { #recap }
Импортируйте и используйте `BackgroundTasks` с параметрами в функциях‑обработчиках пути и зависимостях, чтобы добавлять фоновые задачи.
@@ -0,0 +1,555 @@
# Большие приложения, в которых много файлов { #bigger-applications-multiple-files }
При построении приложения или веб-API нам редко удается поместить всё в один файл.
**FastAPI** предоставляет удобный инструментарий, который позволяет нам структурировать приложение, сохраняя при этом всю необходимую гибкость.
/// info | Примечание
Если вы раньше использовали Flask, то это аналог шаблонов Flask (Flask's Blueprints).
///
## Пример структуры приложения { #an-example-file-structure }
Давайте предположим, что наше приложение имеет следующую структуру:
```
.
├── app
│   ├── __init__.py
│   ├── main.py
│   ├── dependencies.py
│   └── routers
│   │ ├── __init__.py
│   │ ├── items.py
│   │ └── users.py
│   └── internal
│   ├── __init__.py
│   └── admin.py
```
/// tip | Подсказка
Обратите внимание, что в каждом каталоге и подкаталоге имеется файл `__init__.py`
Это как раз то, что позволяет импортировать код из одного файла в другой.
Например, в файле `app/main.py` может быть следующая строка:
```
from app.routers import items
```
///
* Всё помещается в каталоге `app`. В нём также находится пустой файл `app/__init__.py`. Таким образом, `app` является "Python-пакетом" (коллекцией модулей Python).
* Он содержит файл `app/main.py`. Данный файл является частью пакета (т.е. находится внутри каталога, содержащего файл `__init__.py`), и, соответственно, он является модулем пакета: `app.main`.
* Он также содержит файл `app/dependencies.py`, который также, как и `app/main.py`, является модулем: `app.dependencies`.
* Здесь также находится подкаталог `app/routers/`, содержащий `__init__.py`. Он является суб-пакетом: `app.routers`.
* Файл `app/routers/items.py` находится внутри пакета `app/routers/`. Таким образом, он является суб-модулем: `app.routers.items`.
* Точно также `app/routers/users.py` является ещё одним суб-модулем: `app.routers.users`.
* Подкаталог `app/internal/`, содержащий файл `__init__.py`, является ещё одним суб-пакетом: `app.internal`.
* А файл `app/internal/admin.py` является ещё одним суб-модулем: `app.internal.admin`.
<img src="/img/tutorial/bigger-applications/package.drawio.svg">
Та же самая файловая структура приложения, но с комментариями:
```
.
├── app # "app" пакет
│   ├── __init__.py # этот файл превращает "app" в "Python-пакет"
│   ├── main.py # модуль "main", напр.: import app.main
│   ├── dependencies.py # модуль "dependencies", напр.: import app.dependencies
│   └── routers # суб-пакет "routers"
│   │ ├── __init__.py # превращает "routers" в суб-пакет
│   │ ├── items.py # суб-модуль "items", напр.: import app.routers.items
│   │ └── users.py # суб-модуль "users", напр.: import app.routers.users
│   └── internal # суб-пакет "internal"
│   ├── __init__.py # превращает "internal" в суб-пакет
│   └── admin.py # суб-модуль "admin", напр.: import app.internal.admin
```
## `APIRouter` { #apirouter }
Давайте предположим, что для работы с пользователями используется отдельный файл (суб-модуль) `/app/routers/users.py`.
Для лучшей организации приложения, вы хотите отделить операции пути, связанные с пользователями, от остального кода.
Но так, чтобы эти операции по-прежнему оставались частью **FastAPI** приложения/веб-API (частью одного пакета)
С помощью `APIRouter` вы можете создать *операции пути* (*эндпоинты*) для данного модуля.
### Импорт `APIRouter` { #import-apirouter }
Точно также, как и в случае с классом `FastAPI`, вам нужно импортировать и создать объект класса `APIRouter`.
```Python hl_lines="1 3" title="app/routers/users.py"
{!../../docs_src/bigger_applications/app/routers/users.py!}
```
### Создание *эндпоинтов* с помощью `APIRouter` { #path-operations-with-apirouter }
В дальнейшем используйте `APIRouter` для объявления *эндпоинтов*, точно также, как вы используете класс `FastAPI`:
```Python hl_lines="6 11 16" title="app/routers/users.py"
{!../../docs_src/bigger_applications/app/routers/users.py!}
```
Вы можете думать об `APIRouter` как об "уменьшенной версии" класса FastAPI`.
`APIRouter` поддерживает все те же самые опции.
`APIRouter` поддерживает все те же самые параметры, такие как `parameters`, `responses`, `dependencies`, `tags`, и т. д.
/// tip | Подсказка
В данном примере, в качестве названия переменной используется `router`, но вы можете использовать любое другое имя.
///
Мы собираемся подключить данный `APIRouter` к нашему основному приложению на `FastAPI`, но сначала давайте проверим зависимости и создадим ещё один модуль с `APIRouter`.
## Зависимости { #dependencies }
Нам понадобятся некоторые зависимости, которые мы будем использовать в разных местах нашего приложения.
Мы поместим их в отдельный модуль `dependencies` (`app/dependencies.py`).
Теперь мы воспользуемся простой зависимостью, чтобы прочитать кастомизированный `X-Token` из заголовка:
//// tab | Python 3.9+
```Python hl_lines="3 6-8" title="app/dependencies.py"
{!> ../../docs_src/bigger_applications/app_an_py39/dependencies.py!}
```
////
//// tab | Python 3.8+
```Python hl_lines="1 5-7" title="app/dependencies.py"
{!> ../../docs_src/bigger_applications/app_an/dependencies.py!}
```
////
//// tab | Python 3.8+ non-Annotated
/// tip | Подсказка
Мы рекомендуем использовать версию `Annotated`, когда это возможно.
///
```Python hl_lines="1 4-6" title="app/dependencies.py"
{!> ../../docs_src/bigger_applications/app/dependencies.py!}
```
////
/// tip | Подсказка
Для простоты мы воспользовались неким воображаемым заголовоком.
В реальных случаях для получения наилучших результатов используйте интегрированные [утилиты безопасности](security/index.md){.internal-link target=_blank}.
///
## Ещё один модуль с `APIRouter` { #another-module-with-apirouter }
Давайте также предположим, что у вас есть *эндпоинты*, отвечающие за обработку "items", и они находятся в модуле `app/routers/items.py`.
У вас определены следующие *операции пути* (*эндпоинты*):
* `/items/`
* `/items/{item_id}`
Тут всё точно также, как и в ситуации с `app/routers/users.py`.
Но теперь мы хотим поступить немного умнее и слегка упростить код.
Мы знаем, что все *эндпоинты* данного модуля имеют некоторые общие свойства:
* Префикс пути: `/items`.
* Теги: (один единственный тег: `items`).
* Дополнительные ответы (responses)
* Зависимости: использование созданной нами зависимости `X-token`
Таким образом, вместо того чтобы добавлять все эти свойства в функцию каждого отдельного *эндпоинта*,
мы добавим их в `APIRouter`.
```Python hl_lines="5-10 16 21" title="app/routers/items.py"
{!../../docs_src/bigger_applications/app/routers/items.py!}
```
Так как каждый *эндпоинт* начинается с символа `/`:
```Python hl_lines="1"
@router.get("/{item_id}")
async def read_item(item_id: str):
...
```
...то префикс не должен заканчиваться символом `/`.
В нашем случае префиксом является `/items`.
Мы также можем добавить в наш маршрутизатор (router) список `тегов` (`tags`) и дополнительных `ответов` (`responses`), которые являются общими для каждого *эндпоинта*.
И ещё мы можем добавить в наш маршрутизатор список `зависимостей`, которые должны вызываться при каждом обращении к *эндпоинтам*.
/// tip | Подсказка
Обратите внимание, что также, как и в случае с зависимостями в декораторах *эндпоинтов* ([зависимости в декораторах операций пути](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), никакого значения в *функцию эндпоинта* передано не будет.
///
В результате мы получим следующие эндпоинты:
* `/items/`
* `/items/{item_id}`
...как мы и планировали.
* Они будут помечены тегами из заданного списка, в нашем случае это `"items"`.
* Эти теги особенно полезны для системы автоматической интерактивной документации (с использованием OpenAPI).
* Каждый из них будет включать предопределенные ответы `responses`.
* Каждый *эндпоинт* будет иметь список зависимостей (`dependencies`), исполняемых перед вызовом *эндпоинта*.
* Если вы определили зависимости в самой операции пути, **то она также будет выполнена**.
* Сначала выполняются зависимости маршрутизатора, затем вызываются [зависимости в декораторе](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, и, наконец, обычные параметрические зависимости.
* Вы также можете добавить [зависимости `Security` с `scopes`](../advanced/security/oauth2-scopes.md){.internal-link target=_blank}.
/// tip | Подсказка
Например, с помощью зависимостей в `APIRouter` мы можем потребовать аутентификации для доступа ко всей группе *эндпоинтов*, не указывая зависимости для каждой отдельной функции *эндпоинта*.
///
/// check | Заметка
Параметры `prefix`, `tags`, `responses` и `dependencies` относятся к функционалу **FastAPI**, помогающему избежать дублирования кода.
///
### Импорт зависимостей { #import-the-dependencies }
Наш код находится в модуле `app.routers.items` (файл `app/routers/items.py`).
И нам нужно вызвать функцию зависимости из модуля `app.dependencies` (файл `app/dependencies.py`).
Мы используем операцию относительного импорта `..` для импорта зависимости:
```Python hl_lines="3" title="app/routers/items.py"
{!../../docs_src/bigger_applications/app/routers/items.py!}
```
#### Как работает относительный импорт? { #how-relative-imports-work }
/// tip | Подсказка
Если вы прекрасно знаете, как работает импорт в Python, то переходите к следующему разделу.
///
Одна точка `.`, как в данном примере:
```Python
from .dependencies import get_token_header
```
означает:
* Начните с пакета, в котором находится данный модуль (файл `app/routers/items.py` расположен в каталоге `app/routers/`)...
* ... найдите модуль `dependencies` (файл `app/routers/dependencies.py`)...
* ... и импортируйте из него функцию `get_token_header`.
К сожалению, такого файла не существует, и наши зависимости находятся в файле `app/dependencies.py`.
Вспомните, как выглядит файловая структура нашего приложения:
<img src="/img/tutorial/bigger-applications/package.drawio.svg">
---
Две точки `..`, как в данном примере:
```Python
from ..dependencies import get_token_header
```
означают:
* Начните с пакета, в котором находится данный модуль (файл `app/routers/items.py` находится в каталоге `app/routers/`)...
* ... перейдите в родительский пакет (каталог `app/`)...
* ... найдите в нём модуль `dependencies` (файл `app/dependencies.py`)...
* ... и импортируйте из него функцию `get_token_header`.
Это работает верно! 🎉
---
Аналогично, если бы мы использовали три точки `...`, как здесь:
```Python
from ...dependencies import get_token_header
```
то это бы означало:
* Начните с пакета, в котором находится данный модуль (файл `app/routers/items.py` находится в каталоге `app/routers/`)...
* ... перейдите в родительский пакет (каталог `app/`)...
* ... затем перейдите в родительский пакет текущего пакета (такого пакета не существует, `app` находится на самом верхнем уровне 😱)...
* ... найдите в нём модуль `dependencies` (файл `app/dependencies.py`)...
* ... и импортируйте из него функцию `get_token_header`.
Это будет относиться к некоторому пакету, находящемуся на один уровень выше чем `app/` и содержащему свой собственный файл `__init__.py`. Но ничего такого у нас нет. Поэтому это приведет к ошибке в нашем примере. 🚨
Теперь вы знаете, как работает импорт в Python, и сможете использовать относительное импортирование в своих собственных приложениях любого уровня сложности. 🤓
### Добавление пользовательских тегов (`tags`), ответов (`responses`) и зависимостей (`dependencies`) { #add-some-custom-tags-responses-and-dependencies }
Мы не будем добавлять префикс `/items` и список тегов `tags=["items"]` для каждого *эндпоинта*, т.к. мы уже их добавили с помощью `APIRouter`.
Но помимо этого мы можем добавить новые теги для каждого отдельного *эндпоинта*, а также некоторые дополнительные ответы (`responses`), характерные для данного *эндпоинта*:
```Python hl_lines="30-31" title="app/routers/items.py"
{!../../docs_src/bigger_applications/app/routers/items.py!}
```
/// tip | Подсказка
Последний *эндпоинт* будет иметь следующую комбинацию тегов: `["items", "custom"]`.
А также в его документации будут содержаться оба ответа: один для `404` и другой для `403`.
///
## Модуль main в `FastAPI` { #the-main-fastapi }
Теперь давайте посмотрим на модуль `app/main.py`.
Именно сюда вы импортируете и именно здесь вы используете класс `FastAPI`.
Это основной файл вашего приложения, который объединяет всё в одно целое.
И теперь, когда большая часть логики приложения разделена на отдельные модули, основной файл `app/main.py` будет достаточно простым.
### Импорт `FastAPI` { #import-fastapi }
Вы импортируете и создаете класс `FastAPI` как обычно.
Мы даже можем объявить [глобальные зависимости](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` { #import-the-apirouter }
Теперь мы импортируем другие суб-модули, содержащие `APIRouter`:
```Python hl_lines="4-5" title="app/main.py"
{!../../docs_src/bigger_applications/app/main.py!}
```
Так как файлы `app/routers/users.py` и `app/routers/items.py` являются суб-модулями одного и того же Python-пакета `app`, то мы сможем их импортировать, воспользовавшись операцией относительного импорта `.`.
### Как работает импорт? { #how-the-importing-works }
Данная строка кода:
```Python
from .routers import items, users
```
означает:
* Начните с пакета, в котором содержится данный модуль (файл `app/main.py` содержится в каталоге `app/`)...
* ... найдите суб-пакет `routers` (каталог `app/routers/`)...
* ... и из него импортируйте суб-модули `items` (файл `app/routers/items.py`) и `users` (файл `app/routers/users.py`)...
В модуле `items` содержится переменная `router` (`items.router`), та самая, которую мы создали в файле `app/routers/items.py`, она является объектом класса `APIRouter`.
И затем мы сделаем то же самое для модуля `users`.
Мы также могли бы импортировать и другим методом:
```Python
from app.routers import items, users
```
/// info | Примечание
Первая версия является примером относительного импорта:
```Python
from .routers import items, users
```
Вторая версия является примером абсолютного импорта:
```Python
from app.routers import items, users
```
Узнать больше о пакетах и модулях в Python вы можете из <a href="https://docs.python.org/3/tutorial/modules.html" class="external-link" target="_blank">официальной документации Python о модулях</a>
///
### Избегайте конфликтов имен { #avoid-name-collisions }
Вместо того чтобы импортировать только переменную `router`, мы импортируем непосредственно суб-модуль `items`.
Мы делаем это потому, что у нас есть ещё одна переменная `router` в суб-модуле `users`.
Если бы мы импортировали их одну за другой, как показано в примере:
```Python
from .routers.items import router
from .routers.users import router
```
то переменная `router` из `users` переписал бы переменную `router` из `items`, и у нас не было бы возможности использовать их одновременно.
Поэтому, для того чтобы использовать обе эти переменные в одном файле, мы импортировали соответствующие суб-модули:
```Python hl_lines="5" title="app/main.py"
{!../../docs_src/bigger_applications/app/main.py!}
```
### Подключение маршрутизаторов (`APIRouter`) для `users` и для `items` { #include-the-apirouters-for-users-and-items }
Давайте подключим маршрутизаторы (`router`) из суб-модулей `users` и `items`:
```Python hl_lines="10-11" title="app/main.py"
{!../../docs_src/bigger_applications/app/main.py!}
```
/// info | Примечание
`users.router` содержит `APIRouter` из файла `app/routers/users.py`.
А `items.router` содержит `APIRouter` из файла `app/routers/items.py`.
///
С помощью `app.include_router()` мы можем добавить каждый из маршрутизаторов (`APIRouter`) в основное приложение `FastAPI`.
Он подключит все маршруты заданного маршрутизатора к нашему приложению.
/// note | Технические детали
Фактически, внутри он создаст все *операции пути* для каждой операции пути объявленной в `APIRouter`.
И под капотом всё будет работать так, как будто бы мы имеем дело с одним файлом приложения.
///
/// check | Заметка
При подключении маршрутизаторов не стоит беспокоиться о производительности.
Операция подключения займёт микросекунды и понадобится только при запуске приложения.
Таким образом, это не повлияет на производительность. ⚡
///
### Подключение `APIRouter` с пользовательскими префиксом (`prefix`), тегами (`tags`), ответами (`responses`), и зависимостями (`dependencies`) { #include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies }
Теперь давайте представим, что ваша организация передала вам файл `app/internal/admin.py`.
Он содержит `APIRouter` с некоторыми *эндпоитами* администрирования, которые ваша организация использует для нескольких проектов.
В данном примере это сделать очень просто. Но давайте предположим, что поскольку файл используется для нескольких проектов,
то мы не можем модифицировать его, добавляя префиксы (`prefix`), зависимости (`dependencies`), теги (`tags`), и т.д. непосредственно в `APIRouter`:
```Python hl_lines="3" title="app/internal/admin.py"
{!../../docs_src/bigger_applications/app/internal/admin.py!}
```
Но, несмотря на это, мы хотим использовать кастомный префикс (`prefix`) для подключенного маршрутизатора (`APIRouter`), в результате чего, каждая *операция пути* будет начинаться с `/admin`. Также мы хотим защитить наш маршрутизатор с помощью зависимостей, созданных для нашего проекта. И ещё мы хотим включить теги (`tags`) и ответы (`responses`).
Мы можем применить все вышеперечисленные настройки, не изменяя начальный `APIRouter`. Нам всего лишь нужно передать нужные параметры в `app.include_router()`.
```Python hl_lines="14-17" title="app/main.py"
{!../../docs_src/bigger_applications/app/main.py!}
```
Таким образом, оригинальный `APIRouter` не будет модифицирован, и мы сможем использовать файл `app/internal/admin.py` сразу в нескольких проектах организации.
В результате, в нашем приложении каждый *эндпоинт* модуля `admin` будет иметь:
* Префикс `/admin`.
* Тег `admin`.
* Зависимость `get_token_header`.
* Ответ `418`. 🍵
Это будет иметь место исключительно для `APIRouter` в нашем приложении, и не затронет любой другой код, использующий его.
Например, другие проекты, могут использовать тот же самый `APIRouter` с другими методами аутентификации.
### Подключение отдельного *эндпоинта* { #include-a-path-operation }
Мы также можем добавить *эндпоинт* непосредственно в основное приложение `FastAPI`.
Здесь мы это делаем ... просто, чтобы показать, что это возможно 🤷:
```Python hl_lines="21-23" title="app/main.py"
{!../../docs_src/bigger_applications/app/main.py!}
```
и это будет работать корректно вместе с другими *эндпоинтами*, добавленными с помощью `app.include_router()`.
/// info | Сложные технические детали
**Примечание**: это сложная техническая деталь, которую, скорее всего, **вы можете пропустить**.
---
Маршрутизаторы (`APIRouter`) не "монтируются" по-отдельности и не изолируются от остального приложения.
Это происходит потому, что нужно включить их *эндпоинты* в OpenAPI схему и в интерфейс пользователя.
В силу того, что мы не можем их изолировать и "примонтировать" независимо от остальных, *эндпоинты* клонируются (пересоздаются) и не подключаются напрямую.
///
## Проверка автоматической документации API { #check-the-automatic-api-docs }
Теперь запустите приложение:
<div class="termy">
```console
$ fastapi dev app/main.py
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
Откройте документацию по адресу <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
Вы увидите автоматическую API документацию. Она включает в себя маршруты из суб-модулей, используя верные маршруты, префиксы и теги:
<img src="/img/tutorial/bigger-applications/image01.png">
## Подключение существующего маршрута через новый префикс (`prefix`) { #include-the-same-router-multiple-times-with-different-prefix }
Вы можете использовать `.include_router()` несколько раз с одним и тем же маршрутом, применив различные префиксы.
Это может быть полезным, если нужно предоставить доступ к одному и тому же API через различные префиксы, например, `/api/v1` и `/api/latest`.
Это продвинутый способ, который вам может и не пригодится. Мы приводим его на случай, если вдруг вам это понадобится.
## Включение одного маршрутизатора (`APIRouter`) в другой { #include-an-apirouter-in-another }
Точно так же, как вы включаете `APIRouter` в приложение `FastAPI`, вы можете включить `APIRouter` в другой `APIRouter`:
```Python
router.include_router(other_router)
```
Удостоверьтесь, что вы сделали это до того, как подключить маршрутизатор (`router`) к вашему `FastAPI` приложению, и *эндпоинты* маршрутизатора `other_router` были также подключены.
+60
View File
@@ -0,0 +1,60 @@
# Body - Поля { #body-fields }
Таким же способом, как вы объявляете дополнительную валидацию и метаданные в параметрах *функции обработки пути* с помощью функций `Query`, `Path` и `Body`, вы можете объявлять валидацию и метаданные внутри Pydantic моделей, используя функцию `Field` из Pydantic.
## Импорт `Field` { #import-field }
Сначала вы должны импортировать его:
{* ../../docs_src/body_fields/tutorial001_an_py310.py hl[4] *}
/// warning | Внимание
Обратите внимание, что функция `Field` импортируется непосредственно из `pydantic`, а не из `fastapi`, как все остальные функции (`Query`, `Path`, `Body` и т.д.).
///
## Объявление атрибутов модели { #declare-model-attributes }
Вы можете использовать функцию `Field` с атрибутами модели:
{* ../../docs_src/body_fields/tutorial001_an_py310.py hl[11:14] *}
Функция `Field` работает так же, как `Query`, `Path` и `Body`, у неё такие же параметры и т.д.
/// note | Технические детали
На самом деле, `Query`, `Path` и другие функции, которые вы увидите в дальнейшем, создают объекты подклассов общего класса `Param`, который сам по себе является подклассом `FieldInfo` из Pydantic.
И `Field` (из Pydantic) также возвращает экземпляр `FieldInfo`.
`Body` также напрямую возвращает объекты подкласса `FieldInfo`. И есть и другие, с которыми вы познакомитесь позже, которые являются подклассами класса `Body`.
Помните, что когда вы импортируете `Query`, `Path` и другое из `fastapi`, это фактически функции, которые возвращают специальные классы.
///
/// tip | Подсказка
Обратите внимание, что каждый атрибут модели с типом, значением по умолчанию и `Field` имеет ту же структуру, что и параметр *функции обработки пути* с `Field` вместо `Path`, `Query` и `Body`.
///
## Добавление дополнительной информации { #add-extra-information }
Вы можете объявлять дополнительную информацию в `Field`, `Query`, `Body` и т.п. Она будет включена в сгенерированную JSON схему.
Вы узнаете больше о добавлении дополнительной информации позже в документации, когда будете изучать, как задавать примеры.
/// warning | Внимание
Дополнительные ключи, переданные в функцию `Field`, также будут присутствовать в сгенерированной OpenAPI схеме вашего приложения.
Поскольку эти ключи не являются обязательной частью спецификации OpenAPI, некоторые инструменты OpenAPI, например, [валидатор OpenAPI](https://validator.swagger.io/), могут не работать с вашей сгенерированной схемой.
///
## Резюме { #recap }
Вы можете использовать функцию `Field` из Pydantic, чтобы задавать дополнительную валидацию и метаданные для атрибутов модели.
Вы также можете использовать дополнительные ключевые аргументы, чтобы добавить метаданные JSON схемы.
@@ -0,0 +1,171 @@
# Body - Множество параметров { #body-multiple-parameters }
Теперь, когда мы увидели, как использовать `Path` и `Query` параметры, давайте рассмотрим более продвинутые примеры объявления тела запроса.
## Объединение `Path`, `Query` и параметров тела запроса { #mix-path-query-and-body-parameters }
Во-первых, конечно, вы можете объединять параметры `Path`, `Query` и объявления тела запроса в своих функциях обработки, **FastAPI** автоматически определит, что с ними нужно делать.
Вы также можете объявить параметры тела запроса как необязательные, установив значение по умолчанию, равное `None`:
{* ../../docs_src/body_multiple_params/tutorial001_an_py310.py hl[18:20] *}
/// note | Заметка
Заметьте, что в данном случае параметр `item`, который будет взят из тела запроса, необязателен. Так как было установлено значение `None` по умолчанию.
///
## Несколько параметров тела запроса { #multiple-body-parameters }
В предыдущем примере, *операции пути* ожидали тело запроса в формате JSON, с параметрами, соответствующими атрибутам `Item`, например:
```JSON
{
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2
}
```
Но вы также можете объявить множество параметров тела запроса, например `item` и `user`:
{* ../../docs_src/body_multiple_params/tutorial002_py310.py hl[20] *}
В этом случае **FastAPI** заметит, что в функции есть более одного параметра тела (два параметра, которые являются Pydantic-моделями).
Таким образом, имена параметров будут использоваться в качестве ключей (имён полей) в теле запроса, и будет ожидаться запрос следующего формата:
```JSON
{
"item": {
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2
},
"user": {
"username": "dave",
"full_name": "Dave Grohl"
}
}
```
/// note | Внимание
Обратите внимание, что хотя параметр `item` был объявлен таким же способом, как и раньше, теперь предполагается, что он находится внутри тела с ключом `item`.
///
**FastAPI** сделает автоматическое преобразование из запроса, так что параметр `item` получит своё конкретное содержимое, и то же самое происходит с пользователем `user`.
Произойдёт проверка составных данных, и создание документации в схеме OpenAPI и автоматических документах.
## Отдельные значения в теле запроса { #singular-values-in-body }
Точно так же, как `Query` и `Path` используются для определения дополнительных данных для query и path параметров, **FastAPI** предоставляет аналогичный инструмент - `Body`.
Например, расширяя предыдущую модель, вы можете решить, что вам нужен еще один ключ `importance` в том же теле запроса, помимо параметров `item` и `user`.
Если вы объявите его без указания, какой именно объект (Path, Query, Body и т.п.) ожидаете, то, поскольку это является простым типом данных, **FastAPI** будет считать, что это query-параметр.
Но вы можете указать **FastAPI** обрабатывать его, как ещё один ключ тела запроса, используя `Body`:
{* ../../docs_src/body_multiple_params/tutorial003_an_py310.py hl[23] *}
В этом случае, **FastAPI** будет ожидать тело запроса в формате:
```JSON
{
"item": {
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2
},
"user": {
"username": "dave",
"full_name": "Dave Grohl"
},
"importance": 5
}
```
И всё будет работать так же - преобразование типов данных, валидация, документирование и т.д.
## Множество body и query параметров { #multiple-body-params-and-query }
Конечно, вы также можете объявлять query-параметры в любое время, дополнительно к любым body-параметрам.
Поскольку по умолчанию, отдельные значения интерпретируются как query-параметры, вам не нужно явно добавлять `Query`, вы можете просто сделать так:
```Python
q: Union[str, None] = None
```
Или в Python 3.10 и выше:
```Python
q: str | None = None
```
Например:
{* ../../docs_src/body_multiple_params/tutorial004_an_py310.py hl[28] *}
/// info | Информация
`Body` также имеет все те же дополнительные параметры валидации и метаданных, как у `Query`,`Path` и других, которые вы увидите позже.
///
## Вложить один body-параметр { #embed-a-single-body-parameter }
Предположим, у вас есть только один body-параметр `item` из Pydantic-модели `Item`.
По умолчанию, **FastAPI** ожидает получить тело запроса напрямую.
Но если вы хотите чтобы он ожидал JSON с ключом `item` с содержимым модели внутри, также как это происходит при объявлении дополнительных body-параметров, вы можете использовать специальный параметр `embed` у типа `Body`:
```Python
item: Item = Body(embed=True)
```
так же, как в этом примере:
{* ../../docs_src/body_multiple_params/tutorial005_an_py310.py hl[17] *}
В этом случае **FastAPI** будет ожидать тело запроса в формате:
```JSON hl_lines="2"
{
"item": {
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2
}
}
```
вместо этого:
```JSON
{
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2
}
```
## Резюме { #recap }
Вы можете добавлять несколько body-параметров вашей *функции-обработчика пути*, несмотря даже на то, что запрос может содержать только одно тело.
Но **FastAPI** справится с этим, предоставит правильные данные в вашей функции, а также сделает валидацию и документацию правильной схемы *операции пути*.
Вы также можете объявить отдельные значения для получения в рамках тела запроса.
И вы можете настроить **FastAPI** таким образом, чтобы включить тело запроса в ключ, даже если объявлен только один параметр.
+247
View File
@@ -0,0 +1,247 @@
# Body - Вложенные модели { #body-nested-models }
С помощью **FastAPI** вы можете определять, валидировать, документировать и использовать модели произвольной глубины вложенности (благодаря Pydantic).
## Поля-списки { #list-fields }
Вы можете определить атрибут как подтип. Например, Python-тип `list`:
{* ../../docs_src/body_nested_models/tutorial001_py310.py hl[12] *}
Это приведёт к тому, что `tags` будет списком, несмотря на то, что тип его элементов не объявлен.
## Поля-списки с параметром типа { #list-fields-with-type-parameter }
В Python есть специальный способ объявлять списки с внутренними типами, или «параметрами типа»:
### Импортируйте `List` из модуля typing { #import-typings-list }
В Python 3.9 и выше вы можете использовать стандартный тип `list` для объявления аннотаций типов, как мы увидим ниже. 💡
Но в версиях Python до 3.9 (начиная с 3.6) сначала вам необходимо импортировать `List` из стандартного модуля `typing`:
{* ../../docs_src/body_nested_models/tutorial002.py hl[1] *}
### Объявите `list` с параметром типа { #declare-a-list-with-a-type-parameter }
Для объявления типов, у которых есть параметры типа (внутренние типы), таких как `list`, `dict`, `tuple`:
* Если у вас Python версии ниже 3.9, импортируйте их аналоги из модуля `typing`
* Передайте внутренний(ие) тип(ы) как «параметры типа», используя квадратные скобки: `[` и `]`
В Python 3.9 это будет:
```Python
my_list: list[str]
```
В версиях Python до 3.9 это будет:
```Python
from typing import List
my_list: List[str]
```
Это всё стандартный синтаксис Python для объявления типов.
Используйте этот же стандартный синтаксис для атрибутов модели с внутренними типами.
Таким образом, в нашем примере мы можем явно указать тип данных для поля `tags` как «список строк»:
{* ../../docs_src/body_nested_models/tutorial002_py310.py hl[12] *}
## Типы множеств { #set-types }
Но затем мы подумали и поняли, что теги не должны повторяться, вероятно, это должны быть уникальные строки.
И в Python есть специальный тип данных для множеств уникальных элементов — `set`.
Тогда мы можем объявить поле `tags` как множество строк:
{* ../../docs_src/body_nested_models/tutorial003_py310.py hl[12] *}
С помощью этого, даже если вы получите запрос с повторяющимися данными, они будут преобразованы в множество уникальных элементов.
И когда вы выводите эти данные, даже если исходный набор содержал дубликаты, они будут выведены в виде множества уникальных элементов.
И они также будут соответствующим образом аннотированы / задокументированы.
## Вложенные модели { #nested-models }
У каждого атрибута Pydantic-модели есть тип.
Но этот тип сам может быть другой моделью Pydantic.
Таким образом, вы можете объявлять глубоко вложенные JSON «объекты» с определёнными именами атрибутов, типами и валидацией.
Всё это может быть произвольно вложенным.
### Определение подмодели { #define-a-submodel }
Например, мы можем определить модель `Image`:
{* ../../docs_src/body_nested_models/tutorial004_py310.py hl[7:9] *}
### Использование подмодели как типа { #use-the-submodel-as-a-type }
Также мы можем использовать эту модель как тип атрибута:
{* ../../docs_src/body_nested_models/tutorial004_py310.py hl[18] *}
Это означает, что **FastAPI** будет ожидать тело запроса, аналогичное этому:
```JSON
{
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2,
"tags": ["rock", "metal", "bar"],
"image": {
"url": "http://example.com/baz.jpg",
"name": "The Foo live"
}
}
```
Ещё раз: сделав такое объявление, с помощью **FastAPI** вы получите:
* Поддержку редактора кода (автозавершение и т. д.), даже для вложенных моделей
* Преобразование данных
* Валидацию данных
* Автоматическую документацию
## Особые типы и валидация { #special-types-and-validation }
Помимо обычных простых типов, таких как `str`, `int`, `float` и т.д., вы можете использовать более сложные простые типы, которые наследуются от `str`.
Чтобы увидеть все варианты, которые у вас есть, ознакомьтесь с <a href="https://docs.pydantic.dev/latest/concepts/types/" class="external-link" target="_blank">обзором типов Pydantic</a>. Вы увидите некоторые примеры в следующей главе.
Например, так как в модели `Image` у нас есть поле `url`, то мы можем объявить его как тип `HttpUrl` из Pydantic вместо типа `str`:
{* ../../docs_src/body_nested_models/tutorial005_py310.py hl[2,8] *}
Строка будет проверена на соответствие допустимому URL-адресу и задокументирована в JSON Schema / OpenAPI как таковая.
## Атрибуты, содержащие списки подмоделей { #attributes-with-lists-of-submodels }
Вы также можете использовать модели Pydantic в качестве подтипов для `list`, `set` и т.д.:
{* ../../docs_src/body_nested_models/tutorial006_py310.py hl[18] *}
Такая реализация будет ожидать (конвертировать, валидировать, документировать и т.д.) JSON-содержимое в следующем формате:
```JSON hl_lines="11"
{
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2,
"tags": [
"rock",
"metal",
"bar"
],
"images": [
{
"url": "http://example.com/baz.jpg",
"name": "The Foo live"
},
{
"url": "http://example.com/dave.jpg",
"name": "The Baz"
}
]
}
```
/// info | Информация
Заметьте, что теперь у ключа `images` есть список объектов изображений.
///
## Глубоко вложенные модели { #deeply-nested-models }
Вы можете определять модели с произвольным уровнем вложенности:
{* ../../docs_src/body_nested_models/tutorial007_py310.py hl[7,12,18,21,25] *}
/// info | Информация
Заметьте, что у объекта `Offer` есть список объектов `Item`, которые, в свою очередь, могут содержать необязательный список объектов `Image`
///
## Тела с чистыми списками элементов { #bodies-of-pure-lists }
Если верхний уровень значения тела JSON-объекта представляет собой JSON `array` (в Python — `list`), вы можете объявить тип в параметре функции, так же как в моделях Pydantic:
```Python
images: List[Image]
```
или в Python 3.9 и выше:
```Python
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.
Но вы также не должны беспокоиться об этом, входящие словари автоматически конвертируются, а ваш вывод также автоматически преобразуется в формат JSON.
## Тела запросов с произвольными словарями (`dict`) { #bodies-of-arbitrary-dicts }
Вы также можете объявить тело запроса как `dict` с ключами определённого типа и значениями другого типа.
Без необходимости знать заранее, какие значения являются допустимыми для имён полей/атрибутов (как это было бы в случае с моделями Pydantic).
Это было бы полезно, если вы хотите получить ключи, которые вы ещё не знаете.
---
Другой полезный случай — когда вы хотите, чтобы ключи были другого типа данных, например, `int`.
Именно это мы сейчас и увидим здесь.
В этом случае вы принимаете любой `dict`, пока у него есть ключи типа `int` со значениями типа `float`:
{* ../../docs_src/body_nested_models/tutorial009_py39.py hl[7] *}
/// tip | Совет
Имейте в виду, что JSON поддерживает только ключи типа `str`.
Но Pydantic обеспечивает автоматическое преобразование данных.
Это значит, что даже если клиенты вашего API могут отправлять только строки в качестве ключей, при условии, что эти строки содержат целые числа, Pydantic автоматически преобразует и валидирует эти данные.
А `dict`, который вы получите как `weights`, действительно будет иметь ключи типа `int` и значения типа `float`.
///
## Резюме { #recap }
С помощью **FastAPI** вы получаете максимальную гибкость, предоставляемую моделями Pydantic, сохраняя при этом простоту, краткость и элегантность вашего кода.
И дополнительно вы получаете:
* Поддержку редактора кода (автозавершение доступно везде!)
* Преобразование данных (также известно как парсинг / сериализация)
* Валидацию данных
* Документацию схемы данных
* Автоматическую генерацию документации
+114
View File
@@ -0,0 +1,114 @@
# Body - Обновления { #body-updates }
## Обновление с заменой при помощи `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-базе данных). Например, преобразование `datetime` в `str`.
{* ../../docs_src/body_updates/tutorial001_py310.py hl[28:33] *}
`PUT` используется для получения данных, которые должны полностью заменить существующие данные.
### Предупреждение о замене { #warning-about-replacing }
Это означает, что если вы хотите обновить элемент `bar`, используя `PUT` с телом, содержащим:
```Python
{
"name": "Barz",
"price": 3,
"description": None,
}
```
поскольку оно не включает уже сохраненный атрибут `"tax": 20.2`, входная модель примет значение по умолчанию `"tax": 10.5`.
И данные будут сохранены с этим "новым" `tax`, равным `10,5`.
## Частичное обновление с помощью `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> операцию для *частичного* обновления данных.
Это означает, что можно передавать только те данные, которые необходимо обновить, оставляя остальные нетронутыми.
/// note | Технические детали
`PATCH` менее распространен и известен, чем `PUT`.
А многие команды используют только `PUT`, даже для частичного обновления.
Вы можете **свободно** использовать их как угодно, **FastAPI** не накладывает никаких ограничений.
Но в данном руководстве более или менее понятно, как они должны использоваться.
///
### Использование параметра `exclude_unset` в Pydantic { #using-pydantics-exclude-unset-parameter }
Если необходимо выполнить частичное обновление, то очень полезно использовать параметр `exclude_unset` в методе `.model_dump()` модели Pydantic.
Например, `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 { #using-pydantics-update-parameter }
Теперь можно создать копию существующей модели, используя `.model_copy()`, и передать параметр `update` с `dict`, содержащим данные для обновления.
/// 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 }
В целом, для применения частичных обновлений необходимо:
* (Опционально) использовать `PATCH` вместо `PUT`.
* Извлечь сохранённые данные.
* Поместить эти данные в Pydantic модель.
* Сгенерировать `dict` без значений по умолчанию из входной модели (с использованием `exclude_unset`).
* Таким образом, можно обновлять только те значения, которые действительно установлены пользователем, вместо того чтобы переопределять значения, уже сохраненные в модели по умолчанию.
* Создать копию хранимой модели, обновив ее атрибуты полученными частичными обновлениями (с помощью параметра `update`).
* Преобразовать скопированную модель в то, что может быть сохранено в вашей БД (например, с помощью `jsonable_encoder`).
* Это сравнимо с повторным использованием метода модели `.model_dump()`, но при этом происходит проверка (и преобразование) значений в типы данных, которые могут быть преобразованы в JSON, например, `datetime` в `str`.
* Сохранить данные в своей БД.
* Вернуть обновленную модель.
{* ../../docs_src/body_updates/tutorial002_py310.py hl[28:35] *}
/// tip | Подсказка
Эту же технику можно использовать и для операции HTTP `PUT`.
Но в приведенном примере используется `PATCH`, поскольку он был создан именно для таких случаев использования.
///
/// note | Технические детали
Обратите внимание, что входная модель по-прежнему валидируется.
Таким образом, если вы хотите получать частичные обновления, в которых могут быть опущены все атрибуты, вам необходимо иметь модель, в которой все атрибуты помечены как необязательные (со значениями по умолчанию или `None`).
Чтобы отличить модели со всеми необязательными значениями для **обновления** от моделей с обязательными значениями для **создания**, можно воспользоваться идеями, описанными в [Дополнительные модели](extra-models.md){.internal-link target=_blank}.
///
+172
View File
@@ -0,0 +1,172 @@
# Тело запроса { #request-body }
Когда вам необходимо отправить данные из клиента (например, браузера) в ваш API, вы отправляете их как **тело запроса**.
Тело **запроса** — это данные, отправляемые клиентом в ваш API. Тело **ответа** — это данные, которые ваш API отправляет клиенту.
Ваш API почти всегда должен отправлять тело **ответа**. Но клиентам не обязательно всегда отправлять **тело запроса**: иногда они запрашивают только путь, возможно с некоторыми параметрами запроса, но без тела.
Чтобы объявить тело **запроса**, используйте модели <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a>, со всей их мощью и преимуществами.
/// info | Информация
Чтобы отправить данные, используйте один из методов: `POST` (чаще всего), `PUT`, `DELETE` или `PATCH`.
Отправка тела с запросом `GET` имеет неопределённое поведение в спецификациях, тем не менее это поддерживается FastAPI, но только для очень сложных/крайних случаев использования.
Поскольку это не рекомендуется, интерактивная документация со Swagger UI не будет отображать информацию для тела при использовании `GET`, а промежуточные прокси-серверы могут не поддерживать такой вариант запроса.
///
## Импортируйте `BaseModel` из Pydantic { #import-pydantics-basemodel }
Первое, что нужно сделать, — импортировать `BaseModel` из пакета `pydantic`:
{* ../../docs_src/body/tutorial001_py310.py hl[2] *}
## Создайте модель данных { #create-your-data-model }
Затем опишите свою модель данных как класс, наследующийся от `BaseModel`.
Используйте стандартные типы Python для всех атрибутов:
{* ../../docs_src/body/tutorial001_py310.py hl[5:9] *}
Так же, как при объявлении параметров запроса: когда атрибут модели имеет значение по умолчанию, он не обязателен. Иначе он обязателен. Используйте `None`, чтобы сделать его просто необязательным.
Например, модель выше описывает такой JSON "объект" (или Python `dict`):
```JSON
{
"name": "Foo",
"description": "An optional description",
"price": 45.2,
"tax": 3.5
}
```
...так как `description` и `tax` являются необязательными (со значением по умолчанию `None`), такой JSON "объект" тоже будет корректным:
```JSON
{
"name": "Foo",
"price": 45.2
}
```
## Объявите её как параметр { #declare-it-as-a-parameter }
Чтобы добавить её в вашу *операцию пути*, объявите её так же, как вы объявляли параметры пути и параметры запроса:
{* ../../docs_src/body/tutorial001_py310.py hl[16] *}
...и укажите тип параметра как созданную вами модель, `Item`.
## Результаты { #results }
Всего лишь с этой аннотацией типов Python **FastAPI**:
* Считает тело запроса как 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 Schema ваших моделей будет частью сгенерированной схемы OpenAPI и будет отображаться в интерактивной документации API:
<img src="/img/tutorial/body/image01.png">
А также они будут использоваться в документации API внутри каждой *операции пути*, где это требуется:
<img src="/img/tutorial/body/image02.png">
## Поддержка редактора кода { #editor-support }
В вашем редакторе кода внутри функции вы получите подсказки по типам и автозавершение повсюду (этого бы не было, если бы вы получали `dict` вместо модели Pydantic):
<img src="/img/tutorial/body/image03.png">
Также вы получите проверку ошибок при некорректных операциях с типами:
<img src="/img/tutorial/body/image04.png">
Это не случайность — весь фреймворк построен вокруг такого дизайна.
И это было тщательно протестировано ещё на этапе проектирования, до реализации, чтобы убедиться, что всё будет работать со всеми редакторами.
В сам Pydantic даже были внесены некоторые изменения для поддержки этого.
Предыдущие скриншоты сделаны в <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:
<img src="/img/tutorial/body/image05.png">
/// 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>.
Он улучшает поддержку моделей Pydantic в редакторе кода, включая:
* автозавершение
* проверки типов
* рефакторинг
* поиск
* инспекции
///
## Использование модели { #use-the-model }
Внутри функции вам доступны все атрибуты объекта модели напрямую:
{* ../../docs_src/body/tutorial002_py310.py *}
/// info | Информация
В Pydantic v1 метод назывался `.dict()`, в Pydantic v2 он был помечен как устаревший (но всё ещё поддерживается) и переименован в `.model_dump()`.
Примеры здесь используют `.dict()` для совместимости с Pydantic v1, но если вы можете использовать Pydantic v2, используйте `.model_dump()`.
///
## Тело запроса + параметры пути { #request-body-path-parameters }
Вы можете одновременно объявить параметры пути и тело запроса.
**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}.
@@ -0,0 +1,76 @@
# Модели параметров cookie { #cookie-parameter-models }
Если у вас есть группа **cookies**, которые связаны между собой, вы можете создать **Pydantic-модель** для их объявления. 🍪
Это позволит вам **переиспользовать модель** в **разных местах**, а также объявить проверки и метаданные сразу для всех параметров. 😎
/// note | Заметка
Этот функционал доступен с версии `0.115.0`. 🤓
///
/// tip | Совет
Такой же подход применяется для `Query`, `Cookie`, и `Header`. 😎
///
## Pydantic-модель для cookies { #cookies-with-a-pydantic-model }
Объявите параметры **cookie**, которые вам нужны, в **Pydantic-модели**, а затем объявите параметр как `Cookie`:
{* ../../docs_src/cookie_param_models/tutorial001_an_py310.py hl[9:12,16] *}
**FastAPI** **извлечёт** данные для **каждого поля** из **cookies**, полученных в запросе, и выдаст вам объявленную Pydantic-модель.
## Проверка сгенерированной документации { #check-the-docs }
Вы можете посмотреть объявленные cookies в графическом интерфейсе Документации по пути `/docs`:
<div class="screenshot">
<img src="/img/tutorial/cookie-param-models/image01.png">
</div>
/// info | Дополнительная информация
Имейте в виду, что, поскольку **браузеры обрабатывают cookies** особым образом и под капотом, они **не** позволят **JavaScript** легко получить доступ к ним.
Если вы перейдёте к **графическому интерфейсу документации API** по пути `/docs`, то сможете увидеть **документацию** по cookies для ваших *операций путей*.
Но даже если вы **заполните данные** и нажмёте "Execute", поскольку графический интерфейс Документации работает с **JavaScript**, cookies не будут отправлены, и вы увидите сообщение об **ошибке** как будто не указывали никаких значений.
///
## Запрет дополнительных cookies { #forbid-extra-cookies }
В некоторых случаях (не особо часто встречающихся) вам может понадобиться **ограничить** cookies, которые вы хотите получать.
Теперь ваш API сам решает, <abbr title="Это шутка, на всякий случай. Это не имеет никакого отношения к согласию на использование cookie, но забавно, что даже API теперь может отклонять несчастные cookies. Съешьте печеньку. 🍪">принимать ли cookies</abbr>. 🤪🍪
Вы можете сконфигурировать Pydantic-модель так, чтобы запретить (`forbid`) любые дополнительные (`extra`) поля:
{* ../../docs_src/cookie_param_models/tutorial002_an_py39.py hl[10] *}
Если клиент попробует отправить **дополнительные cookies**, то в ответ он получит **ошибку**.
Бедные баннеры cookies, они всеми силами пытаются получить ваше согласие — и всё ради того, чтобы <abbr title="Это ещё одна шутка. Не обращайте на меня внимания. Выпейте кофе со своей печенькой. ☕">API его отклонил</abbr>. 🍪
Например, если клиент попытается отправить cookie `santa_tracker` со значением `good-list-please`, то в ответ он получит **ошибку**, сообщающую ему, что cookie `santa_tracker` <abbr title="Санта не одобряет пропажу печенья. 🎅 Ладно, больше никаких шуток про печенье.">не разрешён</abbr>:
```json
{
"detail": [
{
"type": "extra_forbidden",
"loc": ["cookie", "santa_tracker"],
"msg": "Extra inputs are not permitted",
"input": "good-list-please",
}
]
}
```
## Заключение { #summary }
Вы можете использовать **Pydantic-модели** для объявления <abbr title="Съешьте последнюю печеньку, прежде чем уйти. 🍪">**cookies**</abbr> в **FastAPI**. 😎
+45
View File
@@ -0,0 +1,45 @@
# Параметры Cookie { #cookie-parameters }
Вы можете задать параметры Cookie таким же способом, как `Query` и `Path` параметры.
## Импорт `Cookie` { #import-cookie }
Сначала импортируйте `Cookie`:
{* ../../docs_src/cookie_params/tutorial001_an_py310.py hl[3] *}
## Объявление параметров `Cookie` { #declare-cookie-parameters }
Затем объявляйте параметры cookie, используя ту же структуру, что и с `Path` и `Query`.
Вы можете задать значение по умолчанию, а также все дополнительные параметры валидации или аннотации:
{* ../../docs_src/cookie_params/tutorial001_an_py310.py hl[9] *}
/// note | Технические детали
`Cookie` - это класс, родственный `Path` и `Query`. Он также наследуется от общего класса `Param`.
Но помните, что когда вы импортируете `Query`, `Path`, `Cookie` и другое из `fastapi`, это фактически функции, которые возвращают специальные классы.
///
/// info | Дополнительная информация
Для объявления cookies, вам нужно использовать `Cookie`, иначе параметры будут интерпретированы как параметры запроса.
///
/// info | Дополнительная информация
Имейте в виду, что, поскольку браузеры обрабатывают cookies особым образом и «за кулисами», они не позволяют JavaScript просто так получать к ним доступ.
Если вы откроете интерфейс документации API на `/docs`, вы сможете увидеть документацию по cookies для ваших операций пути.
Но даже если вы заполните данные и нажмёте «Execute», поскольку UI документации работает с JavaScript, cookies отправлены не будут, и вы увидите сообщение об ошибке, как будто вы не указали никаких значений.
///
## Резюме { #recap }
Объявляйте cookies с помощью `Cookie`, используя тот же общий шаблон, что и `Query`, и `Path`.
+88
View File
@@ -0,0 +1,88 @@
# 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://localhost`
* `https://localhost`
* `http://localhost:8080`
Даже если они все расположены в `localhost`, они используют разные протоколы или порты, а значит, являются разными источниками.
## Шаги { #steps }
Допустим, у вас есть фронтенд, запущенный в браузере по адресу `http://localhost:8080`, и его JavaScript-код пытается взаимодействовать с бэкендом, запущенным по адресу `http://localhost` (поскольку мы не указали порт, браузер по умолчанию будет использовать порт `80`).
Затем браузер отправит на бэкенд на `:80` HTTP-запрос `OPTIONS`, и если бэкенд вернёт соответствующие HTTP-заголовки, авторизующие взаимодействие с другим источником (`http://localhost:8080`), то браузер на `:8080` разрешит JavaScript на фронтенде отправить свой запрос на бэкенд на `:80`.
Чтобы это работало, у бэкенда на `:80` должен быть список "разрешённых источников" ("allowed origins").
В таком случае этот список должен содержать `http://localhost:8080`, чтобы фронтенд на `:8080` работал корректно.
## Подстановочный символ "*" { #wildcards }
В качестве списка источников можно указать подстановочный символ `"*"` ("wildcard"), чтобы разрешить любые источники.
Но тогда будут разрешены только некоторые виды взаимодействия, и всё, что связано с учётными данными, будет исключено: куки, HTTP-заголовки Authorization, как при использовании Bearer-токенов, и т.п.
Поэтому, чтобы всё работало корректно, лучше явно указывать список разрешённых источников.
## Использование `CORSMiddleware` { #use-corsmiddleware }
Вы можете настроить это в вашем **FastAPI**-приложении, используя `CORSMiddleware`.
* Импортируйте `CORSMiddleware`.
* Создайте список разрешённых источников (в виде строк).
* Добавьте его как "middleware" (промежуточный слой) к вашему **FastAPI**-приложению.
Вы также можете указать, разрешает ли ваш бэкенд использование:
* Учётных данных (HTTP-заголовки Authorization, куки и т.п.).
* Отдельных HTTP-методов (`POST`, `PUT`) или всех вместе, используя `"*"`.
* Отдельных HTTP-заголовков или всех вместе, используя `"*"`.
{* ../../docs_src/cors/tutorial001.py hl[2,6:11,13:19] *}
`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`, `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-preflight-requests }
Это любые `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>.
/// note | Технические детали
Вы также можете использовать `from starlette.middleware.cors import CORSMiddleware`.
**FastAPI** предоставляет несколько middleware в `fastapi.middleware` только для вашего удобства как разработчика. Но большинство доступных middleware взяты напрямую из Starlette.
///
+113
View File
@@ -0,0 +1,113 @@
# Отладка { #debugging }
Вы можете подключить отладчик в своем редакторе, например, в Visual Studio Code или PyCharm.
## Вызов `uvicorn` { #call-uvicorn }
В вашем FastAPI приложении, импортируйте и вызовите `uvicorn` напрямую:
{* ../../docs_src/debugging/tutorial001.py hl[1,15] *}
### Описание `__name__ == "__main__"` { #about-name-main }
Главная цель использования `__name__ == "__main__"` в том, чтобы код выполнялся при запуске файла с помощью:
<div class="termy">
```console
$ python myapp.py
```
</div>
но не вызывался, когда другой файл импортирует это, например:
```Python
from myapp import app
```
#### Больше деталей { #more-details }
Давайте назовём ваш файл `myapp.py`.
Если вы запустите его с помощью:
<div class="termy">
```console
$ python myapp.py
```
</div>
то встроенная переменная `__name__`, автоматически создаваемая Python в вашем файле, будет иметь значение строкового типа `"__main__"`.
Тогда выполнится условие и эта часть кода:
```Python
uvicorn.run(app, host="0.0.0.0", port=8000)
```
будет запущена.
---
Но этого не произойдет, если вы импортируете этот модуль (файл).
Таким образом, если у вас есть файл `importer.py` с таким импортом:
```Python
from myapp import app
# Some more code
```
то автоматическая создаваемая внутри файла `myapp.py` переменная `__name__` будет иметь значение отличающееся от `"__main__"`.
Следовательно, строка:
```Python
uvicorn.run(app, host="0.0.0.0", port=8000)
```
не будет выполнена.
/// info | Информация
Для получения дополнительной информации, ознакомьтесь с <a href="https://docs.python.org/3/library/__main__.html" class="external-link" target="_blank">официальной документацией Python</a>.
///
## Запуск вашего кода с помощью отладчика { #run-your-code-with-your-debugger }
Так как вы запускаете сервер Uvicorn непосредственно из вашего кода, вы можете вызвать Python программу (ваше FastAPI приложение) напрямую из отладчика.
---
Например, в Visual Studio Code вы можете выполнить следующие шаги:
* Перейдите на панель "Debug".
* Выберите "Add configuration...".
* Выберите "Python"
* Запустите отладчик "`Python: Current File (Integrated Terminal)`".
Это запустит сервер с вашим **FastAPI** кодом, остановится на точках останова, и т.д.
Вот как это может выглядеть:
<img src="/img/tutorial/debugging/image01.png">
---
Если используете Pycharm, вы можете выполнить следующие шаги:
* Открыть "Run" меню.
* Выбрать опцию "Debug...".
* Затем в появившемся контекстном меню.
* Выбрать файл для отладки (в данном случае, `main.py`).
Это запустит сервер с вашим **FastAPI** кодом, остановится на точках останова, и т.д.
Вот как это может выглядеть:
<img src="/img/tutorial/debugging/image02.png">
@@ -0,0 +1,288 @@
# Классы как зависимости { #classes-as-dependencies }
Прежде чем углубиться в систему **Внедрения Зависимостей**, давайте обновим предыдущий пример.
## `dict` из предыдущего примера { #a-dict-from-the-previous-example }
В предыдущем примере мы возвращали `dict` из нашей зависимости:
{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[9] *}
Но затем мы получаем `dict` в параметре `commons` *функции-обработчика пути*.
И мы знаем, что редакторы кода не могут обеспечить достаточную поддержку (например, автозавершение) для `dict`, поскольку они не могут знать их ключи и типы значений.
Мы можем сделать лучше...
## Что делает зависимость { #what-makes-a-dependency }
До сих пор вы видели зависимости, объявленные как функции.
Но это не единственный способ объявления зависимостей (хотя он, вероятно, более распространенный).
Ключевым фактором является то, что зависимость должна быть «вызываемой».
В Python «**вызываемый**» — это всё, что Python может «вызвать», как функцию.
Так, если у вас есть объект `something` (который может и _не_ быть функцией) и вы можете «вызвать» его (выполнить) так:
```Python
something()
```
или
```Python
something(some_argument, some_keyword_argument="foo")
```
в таком случае он является «вызываемым».
## Классы как зависимости { #classes-as-dependencies_1 }
Вы можете заметить, что для создания экземпляра класса в Python используется тот же синтаксис.
Например:
```Python
class Cat:
def __init__(self, name: str):
self.name = name
fluffy = Cat(name="Mr Fluffy")
```
В данном случае `fluffy` является экземпляром класса `Cat`.
А чтобы создать `fluffy`, вы «вызываете» `Cat`.
Таким образом, класс в Python также является **вызываемым**.
Тогда в **FastAPI** в качестве зависимости можно использовать класс Python.
На самом деле FastAPI проверяет, что переданный объект является «вызываемым» (функция, класс или что-либо еще) и какие параметры у него определены.
Если вы передаёте «вызываемый» объект в качестве зависимости в **FastAPI**, он проанализирует параметры, необходимые для этого «вызываемого» объекта, и обработает их так же, как параметры *функции-обработчика пути*. Включая подзависимости.
Это относится и к вызываемым объектам без параметров. Работа с ними происходит точно так же, как и для *функций-обработчиков пути* без параметров.
Теперь мы можем изменить зависимость `common_parameters`, указанную выше, на класс `CommonQueryParams`:
{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[11:15] *}
Обратите внимание на метод `__init__`, используемый для создания экземпляра класса:
{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[12] *}
...он имеет те же параметры, что и ранее используемая функция `common_parameters`:
{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[8] *}
Эти параметры и будут использоваться **FastAPI** для «решения» зависимости.
В обоих случаях она будет иметь:
* Необязательный параметр запроса `q`, представляющий собой `str`.
* Параметр запроса `skip`, представляющий собой `int`, по умолчанию `0`.
* Параметр запроса `limit`, представляющий собой `int`, по умолчанию `100`.
В обоих случаях данные будут конвертированы, валидированы, задокументированы в схеме OpenAPI и т.д.
## Как это использовать { #use-it }
Теперь вы можете объявить свою зависимость, используя этот класс.
{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[19] *}
**FastAPI** вызывает класс `CommonQueryParams`. При этом создается «экземпляр» этого класса, который будет передан в качестве параметра `commons` в вашу функцию.
## Аннотация типа и `Depends` { #type-annotation-vs-depends }
Обратите внимание, что в приведенном выше коде мы два раза пишем `CommonQueryParams`:
//// tab | Python 3.8+
```Python
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
```
////
//// tab | Python 3.8+ non-Annotated
/// tip | Подсказка
Рекомендуется использовать версию с `Annotated`, если возможно.
///
```Python
commons: CommonQueryParams = Depends(CommonQueryParams)
```
////
Последний `CommonQueryParams`, в:
```Python
... Depends(CommonQueryParams)
```
...это то, что **FastAPI** будет использовать, чтобы узнать, что является зависимостью.
Из него FastAPI извлечёт объявленные параметры, и именно его FastAPI будет вызывать.
---
В этом случае первый `CommonQueryParams`, в:
//// tab | Python 3.8+
```Python
commons: Annotated[CommonQueryParams, ...
```
////
//// tab | Python 3.8+ non-Annotated
/// tip | Подсказка
Рекомендуется использовать версию с `Annotated`, если возможно.
///
```Python
commons: CommonQueryParams ...
```
////
...не имеет никакого специального значения для **FastAPI**. FastAPI не будет использовать его для преобразования данных, валидации и т.д. (поскольку для этого используется `Depends(CommonQueryParams)`).
На самом деле можно написать просто:
//// tab | Python 3.8+
```Python
commons: Annotated[Any, Depends(CommonQueryParams)]
```
////
//// tab | Python 3.8+ non-Annotated
/// tip | Подсказка
Рекомендуется использовать версию с `Annotated`, если возможно.
///
```Python
commons = Depends(CommonQueryParams)
```
////
...как тут:
{* ../../docs_src/dependencies/tutorial003_an_py310.py hl[19] *}
Но объявление типа приветствуется, так как в этом случае ваш редактор кода будет знать, что будет передано в качестве параметра `commons`, и тогда он сможет помочь вам с автозавершением, проверкой типов и т.д.:
<img src="/img/tutorial/dependencies/image02.png">
## Сокращение { #shortcut }
Но вы видите, что здесь мы имеем некоторое повторение кода, дважды написав `CommonQueryParams`:
//// tab | Python 3.8+
```Python
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
```
////
//// tab | Python 3.8+ non-Annotated
/// tip | Подсказка
Рекомендуется использовать версию с `Annotated`, если возможно.
///
```Python
commons: CommonQueryParams = Depends(CommonQueryParams)
```
////
**FastAPI** предоставляет сокращение для таких случаев, когда зависимость — это *конкретный* класс, который **FastAPI** будет «вызывать» для создания экземпляра этого класса.
Для этих конкретных случаев вы можете сделать следующее.
Вместо того чтобы писать:
//// tab | Python 3.8+
```Python
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
```
////
//// tab | Python 3.8+ non-Annotated
/// tip | Подсказка
Рекомендуется использовать версию с `Annotated`, если возможно.
///
```Python
commons: CommonQueryParams = Depends(CommonQueryParams)
```
////
...следует написать:
//// tab | Python 3.8+
```Python
commons: Annotated[CommonQueryParams, Depends()]
```
////
//// tab | Python 3.8 non-Annotated
/// tip | Подсказка
Рекомендуется использовать версию с `Annotated`, если возможно.
///
```Python
commons: CommonQueryParams = Depends()
```
////
Вы объявляете зависимость как тип параметра и используете `Depends()` без какого-либо параметра, вместо того чтобы *снова* писать полный класс внутри `Depends(CommonQueryParams)`.
Аналогичный пример будет выглядеть следующим образом:
{* ../../docs_src/dependencies/tutorial004_an_py310.py hl[19] *}
...и **FastAPI** будет знать, что делать.
/// tip | Подсказка
Если это покажется вам более запутанным, чем полезным, не обращайте внимания — это вам не *нужно*.
Это просто сокращение. Потому что **FastAPI** заботится о том, чтобы помочь вам свести к минимуму повторение кода.
///
@@ -0,0 +1,69 @@
# Зависимости в декораторах операции пути { #dependencies-in-path-operation-decorators }
В некоторых случаях, возвращаемое значение зависимости не используется внутри *функции операции пути*.
Или же зависимость не возвращает никакого значения.
Но вам всё-таки нужно, чтобы она выполнилась.
Для таких ситуаций, вместо объявления *функции операции пути* с параметром `Depends`, вы можете добавить список зависимостей `dependencies` в *декоратор операции пути*.
## Добавление `dependencies` (зависимостей) в *декоратор операции пути* { #add-dependencies-to-the-path-operation-decorator }
*Декоратор операции пути* получает необязательный аргумент `dependencies`.
Это должен быть `list` состоящий из `Depends()`:
{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[19] *}
Зависимости из dependencies выполнятся так же, как и обычные зависимости. Но их значения (если они были) не будут переданы в *функцию операции пути*.
/// tip | Подсказка
Некоторые редакторы кода определяют неиспользуемые параметры функций и подсвечивают их как ошибку.
Использование `dependencies` в *декораторе операции пути* гарантирует выполнение зависимостей, избегая при этом предупреждений редактора кода и других инструментов.
Это также должно помочь предотвратить путаницу у начинающих разработчиков, которые видят неиспользуемые параметры в коде и могут подумать что в них нет необходимости.
///
/// info | Примечание
В этом примере мы используем выдуманные пользовательские заголовки `X-Key` и `X-Token`.
Но в реальных проектах, при внедрении системы безопасности, вы получите больше пользы используя интегрированные [средства защиты (следующая глава)](../security/index.md){.internal-link target=_blank}.
///
## Исключения в Зависимостях и возвращаемые значения { #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 }
И они могут возвращать значения или нет, эти значения использоваться не будут.
Таким образом, вы можете переиспользовать обычную зависимость (возвращающую значение), которую вы уже используете где-то в другом месте, и хотя значение не будет использоваться, зависимость будет выполнена:
{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[11,16] *}
## Зависимости для группы *операций путей* { #dependencies-for-a-group-of-path-operations }
Позже, читая о том как структурировать большие приложения ([Большие приложения — несколько файлов](../../tutorial/bigger-applications.md){.internal-link target=_blank}), возможно, многофайловые, вы узнаете как объявить единый параметр `dependencies` для всей группы *операций путей*.
## Глобальные Зависимости { #global-dependencies }
Далее мы увидим, как можно добавить dependencies для всего `FastAPI` приложения, так чтобы они применялись к каждой *операции пути*.
@@ -0,0 +1,289 @@
# Зависимости с yield { #dependencies-with-yield }
FastAPI поддерживает зависимости, которые выполняют некоторые <abbr title='иногда также называемые "exit code", "cleanup code", "teardown code", "closing code", "context manager exit code" и т.п.'>дополнительные шаги после завершения</abbr>.
Для этого используйте `yield` вместо `return`, а дополнительные шаги (код) напишите после него.
/// tip | Подсказка
Убедитесь, что используете `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 использует эти два декоратора внутренне.
///
## Зависимость базы данных с помощью `yield` { #a-database-dependency-with-yield }
Например, с его помощью можно создать сессию работы с базой данных и закрыть её после завершения.
Перед созданием ответа будет выполнен только код до и включая оператор `yield`:
{* ../../docs_src/dependencies/tutorial007.py hl[2:4] *}
Значение, полученное из `yield`, внедряется в *операции пути* и другие зависимости:
{* ../../docs_src/dependencies/tutorial007.py hl[4] *}
Код, следующий за оператором `yield`, выполняется после ответа:
{* ../../docs_src/dependencies/tutorial007.py hl[5:6] *}
/// tip | Подсказка
Можно использовать как `async`, так и обычные функции.
**FastAPI** корректно обработает каждый вариант, так же как и с обычными зависимостями.
///
## Зависимость с `yield` и `try` { #a-dependency-with-yield-and-try }
Если использовать блок `try` в зависимости с `yield`, то вы получите любое исключение, которое было выброшено при использовании зависимости.
Например, если какой-то код в какой-то момент в середине, в другой зависимости или в *операции пути*, сделал "откат" транзакции базы данных или создал любую другую ошибку, то вы получите это исключение в своей зависимости.
Таким образом, можно искать конкретное исключение внутри зависимости с помощью `except SomeException`.
Точно так же можно использовать `finally`, чтобы убедиться, что обязательные шаги при выходе выполнены независимо от того, было ли исключение или нет.
{* ../../docs_src/dependencies/tutorial007.py hl[3,5] *}
## Подзависимости с `yield` { #sub-dependencies-with-yield }
Вы можете иметь подзависимости и "деревья" подзависимостей любого размера и формы, и любая из них или все они могут использовать `yield`.
**FastAPI** проследит за тем, чтобы «код выхода» в каждой зависимости с `yield` выполнялся в правильном порядке.
Например, `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_b` нуждается в том, чтобы значение из `dependency_a` (здесь `dep_a`) было доступно для её кода выхода.
{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[18:19,26:27] *}
Точно так же можно иметь часть зависимостей с `yield`, часть — с `return`, и какие-то из них могут зависеть друг от друга.
Либо у вас может быть одна зависимость, которая требует несколько других зависимостей с `yield` и т.д.
Комбинации зависимостей могут быть какими угодно.
**FastAPI** проследит за тем, чтобы всё выполнялось в правильном порядке.
/// note | Технические детали
Это работает благодаря <a href="https://docs.python.org/3/library/contextlib.html" class="external-link" target="_blank">менеджерам контекста</a> в Python.
**FastAPI** использует их внутренне для достижения этого.
///
## Зависимости с `yield` и `HTTPException` { #dependencies-with-yield-and-httpexception }
Вы видели, что можно использовать зависимости с `yield` и иметь блоки `try`, которые пытаются выполнить некоторый код, а затем запускают код выхода в `finally`.
Также вы можете использовать `except`, чтобы поймать вызванное исключение и что-то с ним сделать.
Например, вы можете <abbr title="«raise» дословно - «поднять», но «вызвать», «сгенерировать» или «выбросить» употребляется чаще">вызвать</abbr> другое исключение, например `HTTPException`.
/// tip | Подсказка
Это довольно продвинутая техника, и в большинстве случаев она вам не понадобится, так как вы можете вызывать исключения (включая `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
participant client as Client
participant handler as Exception handler
participant dep as Dep with yield
participant operation as Path Operation
participant tasks as Background tasks
Note over client,operation: Can raise exceptions, including HTTPException
client ->> dep: Start request
Note over dep: Run code up to yield
opt raise Exception
dep -->> handler: Raise Exception
handler -->> client: HTTP error response
end
dep ->> operation: Run dependency, e.g. DB session
opt raise
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
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 -->> tasks: Handle exceptions in the background task code
end
```
/// info | Дополнительная информация
Клиенту будет отправлен только **один ответ**. Это может быть один из ответов об ошибке или ответ от *операции пути*.
После отправки одного из этих ответов никакой другой ответ отправить нельзя.
///
/// tip | Подсказка
Если вы вызовете какое-либо исключение в коде из *функции-обработчика пути*, оно будет передано зависимостям с `yield`, включая `HTTPException`. В большинстве случаев вы захотите повторно вызвать то же самое исключение или новое из зависимости с `yield`, чтобы убедиться, что оно корректно обработано.
///
## Ранний выход и `scope` { #early-exit-and-scope }
Обычно «код выхода» зависимостей с `yield` выполняется **после того, как ответ** отправлен клиенту.
Но если вы знаете, что не будете использовать зависимость после возврата из *функции-обработчика пути*, вы можете использовать `Depends(scope="function")`, чтобы сообщить FastAPI, что он должен закрыть зависимость после возврата из *функции-обработчика пути*, но **до того**, как **ответ будет отправлен**.
{* ../../docs_src/dependencies/tutorial008e_an_py39.py hl[12,16] *}
`Depends()` принимает параметр `scope`, который может быть:
* `"function"`: начать зависимость до *функции-обработчика пути*, которая обрабатывает запрос, завершить зависимость после окончания *функции-обработчика пути*, но **до того**, как ответ будет отправлен обратно клиенту. То есть функция зависимости будет выполнена **вокруг** *функции-обработчика пути*.
* `"request"`: начать зависимость до *функции-обработчика пути*, которая обрабатывает запрос (как и при использовании `"function"`), но завершить **после** того, как ответ будет отправлен обратно клиенту. То есть функция зависимости будет выполнена **вокруг** цикла запроса (**request**) и ответа.
Если не указано и в зависимости есть `yield`, по умолчанию будет `scope` со значением `"request"`.
### `scope` для подзависимостей { #scope-for-sub-dependencies }
Когда вы объявляете зависимость с `scope="request"` (значение по умолчанию), любая подзависимость также должна иметь `scope` равный `"request"`.
Но зависимость со `scope` равным `"function"` может иметь зависимости со `scope` `"function"` и со `scope` `"request"`.
Это потому, что любая зависимость должна иметь возможность выполнить свой код выхода раньше подзависимостей, так как ей может понадобиться использовать их во время своего кода выхода.
```mermaid
sequenceDiagram
participant client as Client
participant dep_req as Зависимость scope="request"
participant dep_func as Зависимость scope="function"
participant operation as Функция-обработчик пути
client ->> dep_req: Запрос
Note over dep_req: Выполнить код до yield
dep_req ->> dep_func: Передать значение
Note over dep_func: Выполнить код до yield
dep_func ->> operation: Выполнить функцию-обработчик пути
operation ->> dep_func: Выход из функции-обработчика пути
Note over dep_func: Выполнить код после yield
Note over dep_func: ✅ Зависимость закрыта
dep_func ->> client: Отправить ответ клиенту
Note over client: Ответ отправлен
Note over dep_req: Выполнить код после yield
Note over dep_req: ✅ Зависимость закрыта
```
## Зависимости с `yield`, `HTTPException`, `except` и фоновыми задачами { #dependencies-with-yield-httpexception-except-and-background-tasks }
Зависимости с `yield` со временем эволюционировали, чтобы покрыть разные сценарии и исправить некоторые проблемы.
Если вы хотите посмотреть, что менялось в разных версиях FastAPI, вы можете прочитать об этом подробнее в продвинутом руководстве: [Продвинутые зависимости — зависимости с `yield`, `HTTPException`, `except` и фоновыми задачами](../../advanced/advanced-dependencies.md#dependencies-with-yield-httpexception-except-and-background-tasks){.internal-link target=_blank}.
## Контекстные менеджеры { #context-managers }
### Что такое «контекстные менеджеры» { #what-are-context-managers }
«Контекстные менеджеры» — это любые объекты Python, которые можно использовать в операторе `with`.
Например, <a href="https://docs.python.org/3/tutorial/inputoutput.html#reading-and-writing-files" class="external-link" target="_blank">можно использовать `with` для чтения файла</a>:
```Python
with open("./somefile.txt") as f:
contents = f.read()
print(contents)
```
Под капотом вызов `open("./somefile.txt")` создаёт объект, называемый «контекстным менеджером».
Когда блок `with` завершается, он обязательно закрывает файл, даже если были исключения.
Когда вы создаёте зависимость с `yield`, **FastAPI** внутренне создаёт для неё менеджер контекста и сочетает его с некоторыми другими связанными инструментами.
### Использование менеджеров контекста в зависимостях с `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>.
Их также можно использовать внутри зависимостей **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`.
Именно это **FastAPI** использует внутренне для зависимостей с `yield`.
Но использовать эти декораторы для зависимостей FastAPI не обязательно (и не стоит).
FastAPI сделает это за вас на внутреннем уровне.
///
@@ -0,0 +1,15 @@
# Глобальные зависимости { #global-dependencies }
Для некоторых типов приложений может потребоваться добавить зависимости ко всему приложению.
Подобно тому, как вы можете [добавлять `dependencies` (зависимости) в *декораторах операций пути*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, вы можете добавлять зависимости сразу ко всему `FastAPI` приложению.
В этом случае они будут применяться ко всем *операциям пути* в приложении:
{* ../../docs_src/dependencies/tutorial012_an_py39.py hl[16] *}
Все способы [добавления `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` для целой группы *операций пути*.
+250
View File
@@ -0,0 +1,250 @@
# Зависимости { #dependencies }
**FastAPI** имеет очень мощную, но интуитивную систему **<abbr title="также известно как: компоненты, ресурсы, провайдеры, сервисы, внедряемые зависимости">Инъекция зависимостей</abbr>**.
Она спроектирована так, чтобы быть очень простой в использовании и облегчать любому разработчику интеграцию других компонентов с **FastAPI**.
## Что такое инъекция зависимостей («Dependency Injection») { #what-is-dependency-injection }
В программировании **«Dependency Injection»** означает, что у вашего кода (в данном случае у ваших *функций обработки пути*) есть способ объявить вещи, которые требуются для его работы и использования: «зависимости».
И затем эта система (в нашем случае **FastAPI**) позаботится о том, чтобы сделать всё необходимое для предоставления вашему коду этих зависимостей (сделать «инъекцию» зависимостей).
Это очень полезно, когда вам нужно:
* Обеспечить общую логику (один и тот же алгоритм снова и снова).
* Разделять соединения с базой данных.
* Обеспечить безопасность, аутентификацию, требования к ролям и т. п.
* И многое другое...
Всё это при минимизации повторения кода.
## Первые шаги { #first-steps }
Давайте рассмотрим очень простой пример. Он настолько простой, что пока не очень полезен.
Но так мы сможем сосредоточиться на том, как работает система **Dependency Injection**.
### Создайте зависимость, или «dependable» (от чего что-то зависит) { #create-a-dependency-or-dependable }
Сначала сосредоточимся на зависимости.
Это просто функция, которая может принимать те же параметры, что и *функция обработки пути*:
{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[8:9] *}
И всё.
**2 строки.**
И она имеет ту же форму и структуру, что и все ваши *функции обработки пути*.
Можно думать о ней как о *функции обработки пути* без «декоратора» (без `@app.get("/some-path")`).
И она может возвращать что угодно.
В этом случае эта зависимость ожидает:
* Необязательный query-параметр `q` типа `str`.
* Необязательный query-параметр `skip` типа `int`, по умолчанию `0`.
* Необязательный query-параметр `limit` типа `int`, по умолчанию `100`.
А затем просто возвращает `dict`, содержащий эти значения.
/// info | Информация
FastAPI добавил поддержку `Annotated` (и начал рекомендовать его использование) в версии 0.95.0.
Если у вас более старая версия, вы получите ошибки при попытке использовать `Annotated`.
Убедитесь, что вы [обновили версию FastAPI](../../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} как минимум до 0.95.1, прежде чем использовать `Annotated`.
///
### Импорт `Depends` { #import-depends }
{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[3] *}
### Объявите зависимость в «зависимом» { #declare-the-dependency-in-the-dependant }
Точно так же, как вы используете `Body`, `Query` и т. д. с параметрами вашей *функции обработки пути*, используйте `Depends` с новым параметром:
{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[13,18] *}
Хотя вы используете `Depends` в параметрах вашей функции так же, как `Body`, `Query` и т. д., `Depends` работает немного иначе.
В `Depends` вы передаёте только один параметр.
Этот параметр должен быть чем-то вроде функции.
Вы **не вызываете её** напрямую (не добавляйте круглые скобки в конце), просто передаёте её как параметр в `Depends()`.
И эта функция принимает параметры так же, как *функции обработки пути*.
/// tip | Подсказка
В следующей главе вы увидите, какие ещё «вещи», помимо функций, можно использовать в качестве зависимостей.
///
Каждый раз, когда приходит новый запрос, **FastAPI** позаботится о:
* Вызове вашей зависимости («dependable») с корректными параметрами.
* Получении результата из вашей функции.
* Присваивании этого результата параметру в вашей *функции обработки пути*.
```mermaid
graph TB
common_parameters(["common_parameters"])
read_items["/items/"]
read_users["/users/"]
common_parameters --> read_items
common_parameters --> read_users
```
Таким образом, вы пишете общий код один раз, а **FastAPI** позаботится о его вызове для ваших *операций пути*.
/// check | Проверка
Обратите внимание, что вам не нужно создавать специальный класс и передавать его куда-то в **FastAPI**, чтобы «зарегистрировать» его или что-то подобное.
Вы просто передаёте его в `Depends`, и **FastAPI** знает, что делать дальше.
///
## Использование зависимости с `Annotated` в нескольких местах { #share-annotated-dependencies }
В приведённых выше примерах есть небольшое **повторение кода**.
Когда вам нужно использовать зависимость `common_parameters()`, вы должны написать весь параметр с аннотацией типа и `Depends()`:
```Python
commons: Annotated[dict, Depends(common_parameters)]
```
Но поскольку мы используем `Annotated`, мы можем сохранить это значение `Annotated` в переменную и использовать его в нескольких местах:
{* ../../docs_src/dependencies/tutorial001_02_an_py310.py hl[12,16,21] *}
/// tip | Подсказка
Это стандартный Python, это называется «type alias», и это не особенность **FastAPI**.
Но поскольку **FastAPI** основан на стандартах Python, включая `Annotated`, вы можете использовать этот трюк в своём коде. 😎
///
Зависимости продолжат работать как ожидалось, и **лучшая часть** в том, что **информация о типах будет сохранена**, а значит, ваш редактор кода продолжит предоставлять **автозавершение**, **встроенные ошибки** и т.д. То же относится и к другим инструментам, таким как `mypy`.
Это особенно полезно, когда вы используете это в **большой кодовой базе**, где вы используете **одни и те же зависимости** снова и снова во **многих *операциях пути***.
## Использовать `async` или не `async` { #to-async-or-not-to-async }
Поскольку зависимости также вызываются **FastAPI** (как и ваши *функции обработки пути*), применяются те же правила при определении ваших функций.
Вы можете использовать `async def` или обычное `def`.
И вы можете объявлять зависимости с `async def` внутри обычных *функций обработки пути* `def`, или зависимости `def` внутри *функций обработки пути* `async def` и т. д.
Это не важно. **FastAPI** знает, что делать.
/// note | Примечание
Если вы не уверены, посмотрите раздел [Async: *"In a hurry?"*](../../async.md#in-a-hurry){.internal-link target=_blank} о `async` и `await` в документации.
///
## Интеграция с OpenAPI { #integrated-with-openapi }
Все объявления запросов, проверки и требования ваших зависимостей (и подзависимостей) будут интегрированы в ту же схему OpenAPI.
Поэтому в интерактивной документации будет вся информация и из этих зависимостей:
<img src="/img/tutorial/dependencies/image01.png">
## Простое использование { #simple-usage }
Если посмотреть, *функции обработки пути* объявляются для использования всякий раз, когда *путь* и *операция* совпадают, и тогда **FastAPI** заботится о вызове функции с корректными параметрами, извлекая данные из запроса.
На самом деле все (или большинство) веб-фреймворков работают таким же образом.
Вы никогда не вызываете эти функции напрямую. Их вызывает ваш фреймворк (в нашем случае **FastAPI**).
С системой **Dependency Injection** вы также можете сообщить **FastAPI**, что ваша *функция обработки пути* «зависит» от чего-то, что должно быть выполнено перед вашей *функцией обработки пути*, и **FastAPI** позаботится о его выполнении и «инъекции» результатов.
Другие распространённые термины для описания той же идеи «dependency injection»:
* ресурсы
* провайдеры
* сервисы
* внедряемые зависимости
* компоненты
## Плагины **FastAPI** { #fastapi-plug-ins }
Интеграции и «плагины» могут быть построены с использованием системы **Dependency Injection**. Но на самом деле **нет необходимости создавать «плагины»**, так как, используя зависимости, можно объявить бесконечное количество интеграций и взаимодействий, которые становятся доступными вашим *функциям обработки пути*.
И зависимости можно создавать очень простым и интуитивным способом, который позволяет просто импортировать нужные пакеты Python и интегрировать их с вашими API-функциями в пару строк кода, *буквально*.
Вы увидите примеры этого в следующих главах о реляционных и NoSQL базах данных, безопасности и т.д.
## Совместимость с **FastAPI** { #fastapi-compatibility }
Простота системы **Dependency Injection** делает **FastAPI** совместимым с:
* всеми реляционными базами данных
* NoSQL базами данных
* внешними пакетами
* внешними API
* системами аутентификации и авторизации
* системами мониторинга использования API
* системами инъекции данных в ответы
* и т.д.
## Просто и мощно { #simple-and-powerful }
Хотя иерархическая система dependency injection очень проста для определения и использования, она по-прежнему очень мощная.
Вы можете определять зависимости, которые, в свою очередь, могут иметь собственные зависимости.
В итоге строится иерархическое дерево зависимостей, и система **Dependency Injection** берёт на себя решение всех этих зависимостей (и их подзависимостей) и предоставляет (инъектирует) результаты на каждом шаге.
Например, у вас есть 4 API-эндпоинта (*операции пути*):
* `/items/public/`
* `/items/private/`
* `/users/{user_id}/activate`
* `/items/pro/`
тогда вы можете добавить разные требования к правам для каждого из них только с помощью зависимостей и подзависимостей:
```mermaid
graph TB
current_user(["current_user"])
active_user(["active_user"])
admin_user(["admin_user"])
paying_user(["paying_user"])
public["/items/public/"]
private["/items/private/"]
activate_user["/users/{user_id}/activate"]
pro_items["/items/pro/"]
current_user --> active_user
active_user --> admin_user
active_user --> paying_user
current_user --> public
active_user --> private
admin_user --> activate_user
paying_user --> pro_items
```
## Интегрировано с **OpenAPI** { #integrated-with-openapi_1 }
Все эти зависимости, объявляя свои требования, также добавляют параметры, проверки и т.д. к вашим *операциям пути*.
**FastAPI** позаботится о добавлении всего этого в схему OpenAPI, чтобы это отображалось в системах интерактивной документации.
@@ -0,0 +1,105 @@
# Подзависимости { #sub-dependencies }
Вы можете создавать зависимости, которые имеют **подзависимости**.
Их **вложенность** может быть любой глубины.
**FastAPI** сам займётся их управлением.
## Первая зависимость { #first-dependency-dependable }
Можно создать первую зависимость следующим образом:
{* ../../docs_src/dependencies/tutorial005_an_py310.py hl[8:9] *}
Она объявляет необязательный параметр запроса `q` как строку, а затем возвращает его.
Это довольно просто (хотя и не очень полезно), но поможет нам сосредоточиться на том, как работают подзависимости.
## Вторая зависимость, «зависимость» и «зависимая» { #second-dependency-dependable-and-dependant }
Затем можно создать еще одну функцию зависимости, которая одновременно объявляет свою собственную зависимость (таким образом, она тоже является «зависимой»):
{* ../../docs_src/dependencies/tutorial005_an_py310.py hl[13] *}
Остановимся на объявленных параметрах:
* Несмотря на то, что эта функция сама является зависимостью, она также является зависимой от чего-то другого.
* Она зависит от `query_extractor` и присваивает возвращаемое ей значение параметру `q`.
* Она также объявляет необязательный куки-параметр `last_query` в виде строки.
* Если пользователь не указал параметр `q` в запросе, то мы используем последний использованный запрос, который мы ранее сохранили в куки-параметре `last_query`.
## Использование зависимости { #use-the-dependency }
Затем мы можем использовать зависимость вместе с:
{* ../../docs_src/dependencies/tutorial005_an_py310.py hl[23] *}
/// info | Дополнительная информация
Обратите внимание, что мы объявляем только одну зависимость в *функции операции пути* - `query_or_cookie_extractor`.
Но **FastAPI** будет знать, что сначала он должен выполнить `query_extractor`, чтобы передать результаты этого в `query_or_cookie_extractor` при его вызове.
///
```mermaid
graph TB
query_extractor(["query_extractor"])
query_or_cookie_extractor(["query_or_cookie_extractor"])
read_query["/items/"]
query_extractor --> query_or_cookie_extractor --> read_query
```
## Использование одной и той же зависимости несколько раз { #using-the-same-dependency-multiple-times }
Если одна из ваших зависимостей объявлена несколько раз для одной и той же *функции операции пути*, например, несколько зависимостей имеют общую подзависимость, **FastAPI** будет знать, что вызывать эту подзависимость нужно только один раз за запрос.
При этом возвращаемое значение будет сохранено в <abbr title="Система для хранения значений, сгенерированных компьютером, для их повторного использования вместо повторного вычисления.">"кэш"</abbr> и будет передано всем "зависимым" функциям, которые нуждаются в нем внутри этого конкретного запроса, вместо того, чтобы вызывать зависимость несколько раз для одного и того же запроса.
В расширенном сценарии, когда вы знаете, что вам нужно, чтобы зависимость вызывалась на каждом шаге (возможно, несколько раз) в одном и том же запросе, вместо использования "кэшированного" значения, вы можете установить параметр `use_cache=False` при использовании `Depends`:
//// tab | Python 3.8+
```Python hl_lines="1"
async def needy_dependency(fresh_value: Annotated[str, Depends(get_value, use_cache=False)]):
return {"fresh_value": fresh_value}
```
////
//// tab | Python 3.8+ без Annotated
/// tip | Подсказка
Предпочтительнее использовать версию с аннотацией, если это возможно.
///
```Python hl_lines="1"
async def needy_dependency(fresh_value: str = Depends(get_value, use_cache=False)):
return {"fresh_value": fresh_value}
```
////
## Резюме { #recap }
Помимо всех этих умных слов, используемых здесь, система внедрения зависимостей довольно проста.
Это просто функции, которые выглядят так же, как *функции операций путей*.
Но, тем не менее, эта система очень мощная и позволяет вам объявлять вложенные графы (деревья) зависимостей сколь угодно глубоко.
/// tip | Подсказка
Все это может показаться не столь полезным на этих простых примерах.
Но вы увидите как это пригодится в главах посвященных безопасности.
И вы также увидите, сколько кода это вам сэкономит.
///
+35
View File
@@ -0,0 +1,35 @@
# JSON-совместимый кодировщик { #json-compatible-encoder }
В некоторых случаях может потребоваться преобразование типа данных (например, Pydantic-модели) в тип, совместимый с JSON (например, `dict`, `list` и т.д.).
Например, если необходимо хранить его в базе данных.
Для этого **FastAPI** предоставляет функцию `jsonable_encoder()`.
## Использование `jsonable_encoder` { #using-the-jsonable-encoder }
Представим, что у вас есть база данных `fake_db`, которая принимает только JSON-совместимые данные.
Например, он не принимает объекты `datetime`, так как они не совместимы с JSON.
В таком случае объект `datetime` следует преобразовать в строку соответствующую <a href="https://en.wikipedia.org/wiki/ISO_8601" class="external-link" target="_blank">формату ISO</a>.
Точно так же эта база данных не может принять Pydantic-модель (объект с атрибутами), а только `dict`.
Для этого можно использовать функцию `jsonable_encoder`.
Она принимает объект, например, Pydantic-модель, и возвращает его версию, совместимую с JSON:
{* ../../docs_src/encoder/tutorial001_py310.py hl[4,21] *}
В данном примере она преобразует 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 | Примечание
`jsonable_encoder` фактически используется **FastAPI** внутри системы для преобразования данных. Однако он полезен и во многих других сценариях.
///
+62
View File
@@ -0,0 +1,62 @@
# Дополнительные типы данных { #extra-data-types }
До сих пор вы использовали простые типы данных, такие как:
* `int`
* `float`
* `str`
* `bool`
Но вы также можете использовать и более сложные типы.
При этом у вас останутся те же возможности, что и до сих пор:
* Отличная поддержка редактора кода.
* Преобразование данных из входящих запросов.
* Преобразование данных для ответа.
* Валидация данных.
* Автоматическая аннотация и документация.
## Другие типы данных { #other-data-types }
Ниже перечислены некоторые из дополнительных типов данных, которые вы можете использовать:
* `UUID`:
* Стандартный "Универсальный уникальный идентификатор", используемый в качестве идентификатора во многих базах данных и системах.
* В запросах и ответах будет представлен как `str`.
* `datetime.datetime`:
* Встроенный в Python `datetime.datetime`.
* В запросах и ответах будет представлен как `str` в формате ISO 8601, например: `2008-09-15T15:53:00+05:00`.
* `datetime.date`:
* Встроенный в Python `datetime.date`.
* В запросах и ответах будет представлен как `str` в формате ISO 8601, например: `2008-09-15`.
* `datetime.time`:
* Встроенный в Python `datetime.time`.
* В запросах и ответах будет представлен как `str` в формате ISO 8601, например: `14:23:55.003`.
* `datetime.timedelta`:
* Встроенный в Python `datetime.timedelta`.
* В запросах и ответах будет представлен в виде общего количества секунд типа `float`.
* Pydantic также позволяет представить его как "Кодировку разницы во времени ISO 8601", <a href="https://docs.pydantic.dev/latest/concepts/serialization/#custom-serializers" class="external-link" target="_blank">см. документацию для получения дополнительной информации</a>.
* `frozenset`:
* В запросах и ответах обрабатывается так же, как и `set`:
* В запросах будет прочитан список, исключены дубликаты и преобразован в `set`.
* В ответах `set` будет преобразован в `list`.
* В сгенерированной схеме будет указано, что значения `set` уникальны (с помощью JSON-схемы `uniqueItems`).
* `bytes`:
* Встроенный в Python `bytes`.
* В запросах и ответах будет рассматриваться как `str`.
* В сгенерированной схеме будет указано, что это `str` в формате `binary`.
* `Decimal`:
* Встроенный в Python `Decimal`.
* В запросах и ответах обрабатывается так же, как и `float`.
* Вы можете проверить все допустимые типы данных 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_an_py310.py hl[1,3,12:16] *}
Обратите внимание, что параметры внутри функции имеют свой естественный тип данных, и вы, например, можете выполнять обычные манипуляции с датами, такие как:
{* ../../docs_src/extra_data_types/tutorial001_an_py310.py hl[18:19] *}
+219
View File
@@ -0,0 +1,219 @@
# Дополнительные модели { #extra-models }
В продолжение прошлого примера будет уже обычным делом иметь несколько связанных между собой моделей.
Это особенно применимо в случае моделей пользователя, потому что:
* **Модель для ввода** должна иметь возможность содержать пароль.
* **Модель для вывода** не должна содержать пароль.
* **Модель для базы данных**, возможно, должна содержать хэшированный пароль.
/// danger | Внимание
Никогда не храните пароли пользователей в чистом виде. Всегда храните "безопасный хэш", который вы затем сможете проверить.
Если вам это не знакомо, вы можете узнать про "хэш пароля" в [главах о безопасности](security/simple-oauth2.md#password-hashing){.internal-link target=_blank}.
///
## Множественные модели { #multiple-models }
Ниже изложена основная идея того, как могут выглядеть эти модели с полями для паролей, а также описаны места, где они используются:
{* ../../docs_src/extra_models/tutorial001_py310.py hl[7,9,14,20,22,27:28,31:33,38:39] *}
/// info | Информация
В 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`.
У Pydantic-моделей есть метод `.dict()`, который возвращает `dict` с данными модели.
Поэтому, если мы создадим Pydantic-объект `user_in` таким способом:
```Python
user_in = UserIn(username="john", password="secret", email="john.doe@example.com")
```
и затем вызовем:
```Python
user_dict = user_in.dict()
```
то теперь у нас есть `dict` с данными модели в переменной `user_dict` (это `dict` вместо объекта Pydantic-модели).
И если мы вызовем:
```Python
print(user_dict)
```
мы можем получить `dict` с такими данными:
```Python
{
'username': 'john',
'password': 'secret',
'email': 'john.doe@example.com',
'full_name': None,
}
```
#### Распаковка `dict` { #unpacking-a-dict }
Если мы возьмём `dict` наподобие `user_dict` и передадим его в функцию (или класс), используя `**user_dict`, Python распакует его. Он передаст ключи и значения `user_dict` напрямую как аргументы типа ключ-значение.
Поэтому, продолжая описанный выше пример с `user_dict`, написание такого кода:
```Python
UserInDB(**user_dict)
```
Будет работать так же, как примерно такой код:
```Python
UserInDB(
username="john",
password="secret",
email="john.doe@example.com",
full_name=None,
)
```
Или, если для большей точности мы напрямую используем `user_dict` с любым потенциальным содержимым, то этот пример будет выглядеть так:
```Python
UserInDB(
username = user_dict["username"],
password = user_dict["password"],
email = user_dict["email"],
full_name = user_dict["full_name"],
)
```
#### Pydantic-модель из содержимого другой модели { #a-pydantic-model-from-the-contents-of-another }
Как в примере выше мы получили `user_dict` из `user_in.dict()`, этот код:
```Python
user_dict = user_in.dict()
UserInDB(**user_dict)
```
будет равнозначен такому:
```Python
UserInDB(**user_in.dict())
```
...потому что `user_in.dict()` - это `dict`, и затем мы указываем, чтобы Python его "распаковал", когда передаём его в `UserInDB` и ставим перед ним `**`.
Таким образом мы получаем Pydantic-модель на основе данных из другой Pydantic-модели.
#### Распаковка `dict` и дополнительные именованные аргументы { #unpacking-a-dict-and-extra-keywords }
И затем, если мы добавим дополнительный именованный аргумент `hashed_password=hashed_password` как здесь:
```Python
UserInDB(**user_in.dict(), hashed_password=hashed_password)
```
... то мы получим что-то подобное:
```Python
UserInDB(
username = user_dict["username"],
password = user_dict["password"],
email = user_dict["email"],
full_name = user_dict["full_name"],
hashed_password = hashed_password,
)
```
/// warning | Предупреждение
Вспомогательные функции `fake_password_hasher` и `fake_save_user` используются только для демонстрации возможного потока данных и, конечно, не обеспечивают настоящую безопасность.
///
## Сократите дублирование { #reduce-duplication }
Сокращение дублирования кода - это одна из главных идей **FastAPI**.
Поскольку дублирование кода повышает риск появления багов, проблем с безопасностью, проблем десинхронизации кода (когда вы обновляете код в одном месте, но не обновляете в другом), и т.д.
А все описанные выше модели используют много общих данных и дублируют названия атрибутов и типов.
Мы можем это улучшить.
Мы можем определить модель `UserBase`, которая будет базовой для остальных моделей. И затем мы можем создать подклассы этой модели, которые будут наследовать её атрибуты (объявления типов, валидацию, и т.п.).
Все операции конвертации, валидации, документации, и т.п. будут по-прежнему работать нормально.
В этом случае мы можем определить только различия между моделями (с `password` в чистом виде, с `hashed_password` и без пароля):
{* ../../docs_src/extra_models/tutorial002_py310.py hl[7,13:14,17:18,21:22] *}
## `Union` или `anyOf` { #union-or-anyof }
Вы можете определить ответ как `Union` из двух или более типов. Это означает, что ответ должен соответствовать одному из них.
Он будет определён в OpenAPI как `anyOf`.
Для этого используйте стандартную аннотацию типов в Python <a href="https://docs.python.org/3/library/typing.html#typing.Union" class="external-link" target="_blank">`typing.Union`</a>:
/// note | Примечание
При объявлении <a href="https://docs.pydantic.dev/latest/concepts/types/#unions" class="external-link" target="_blank">`Union`</a>, сначала указывайте наиболее детальные типы, затем менее детальные. В примере ниже более детальный `PlaneItem` стоит перед `CarItem` в `Union[PlaneItem, CarItem]`.
///
{* ../../docs_src/extra_models/tutorial003_py310.py hl[1,14:15,18:20,33] *}
### `Union` в Python 3.10 { #union-in-python-3-10 }
В этом примере мы передаём `Union[PlaneItem, CarItem]` в качестве значения аргумента `response_model`.
Поскольку мы передаём его как **значение аргумента** вместо того, чтобы поместить его в **аннотацию типа**, нам придётся использовать `Union` даже в Python 3.10.
Если оно было бы указано в аннотации типа, то мы могли бы использовать вертикальную черту как в примере:
```Python
some_variable: PlaneItem | CarItem
```
Но если мы помещаем его в `response_model=PlaneItem | CarItem` мы получим ошибку, потому что Python попытается произвести **некорректную операцию** между `PlaneItem` и `CarItem` вместо того, чтобы интерпретировать это как аннотацию типа.
## Список моделей { #list-of-models }
Таким же образом вы можете определять ответы как списки объектов.
Для этого используйте `typing.List` из стандартной библиотеки Python (или просто `list` в Python 3.9 и выше):
{* ../../docs_src/extra_models/tutorial004_py39.py hl[18] *}
## Ответ с произвольным `dict` { #response-with-arbitrary-dict }
Вы также можете определить ответ, используя произвольный одноуровневый `dict` и определяя только типы ключей и значений без использования Pydantic-моделей.
Это полезно, если вы заранее не знаете корректных названий полей/атрибутов (которые будут нужны при использовании Pydantic-модели).
В этом случае вы можете использовать `typing.Dict` (или просто `dict` в Python 3.9 и выше):
{* ../../docs_src/extra_models/tutorial005_py39.py hl[6] *}
## Резюме { #recap }
Используйте несколько Pydantic-моделей и свободно применяйте наследование для каждой из них.
Вам не обязательно иметь единственную модель данных для каждой сущности, если эта сущность должна иметь возможность быть в разных "состояниях". Как в случае с "сущностью" пользователя, у которого есть состояния с полями `password`, `password_hash` и без пароля.
+323
View File
@@ -0,0 +1,323 @@
# Первые шаги { #first-steps }
Самый простой файл FastAPI может выглядеть так:
{* ../../docs_src/first_steps/tutorial001.py *}
Скопируйте это в файл `main.py`.
Запустите сервер в режиме реального времени:
<div class="termy">
```console
$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:solid">main.py</u>
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting development server 🚀
Searching for package file structure from directories
with <font color="#3465A4">__init__.py</font> files
Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
<span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> 🐍 main.py
<span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with
the following code:
<u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u>
<span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000/docs</u></font>
<span style="background-color:#007166"><font color="#D3D7CF"> tip </font></span> Running in development mode, for production use:
<b>fastapi run</b>
Logs:
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Will watch for changes in these directories:
<b>[</b><font color="#4E9A06">&apos;/home/user/code/awesomeapp&apos;</font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font> <b>(</b>Press CTRL+C
to quit<b>)</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started reloader process <b>[</b><font color="#34E2E2"><b>383138</b></font><b>]</b> using WatchFiles
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>383153</b></font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
```
</div>
В выводе будет строка примерно такого вида:
```hl_lines="4"
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
Эта строка показывает 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
{"message": "Hello World"}
```
### Интерактивная документация 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>.
Вы увидите автоматически сгенерированную интерактивную документацию по API (предоставлено <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>):
![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png)
### Альтернативная документация API { #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="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>):
![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png)
### OpenAPI { #openapi }
**FastAPI** генерирует «схему» всего вашего API, используя стандарт **OpenAPI** для описания API.
#### «Схема» { #schema }
«Схема» — это определение или описание чего-либо. Не код, который это реализует, а только абстрактное описание.
#### «Схема» API { #api-schema }
В данном случае <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> — это спецификация, которая определяет, как описывать схему вашего API.
Это определение схемы включает пути вашего API, возможные параметры, которые они принимают, и т. п.
#### «Схема» данных { #data-schema }
Термин «схема» также может относиться к форме некоторых данных, например, к содержимому JSON.
В таком случае это будут атрибуты JSON, их типы данных и т. п.
#### OpenAPI и JSON Schema { #openapi-and-json-schema }
OpenAPI определяет схему API для вашего API. И эта схема включает определения (или «схемы») данных, отправляемых и получаемых вашим API, с использованием стандарта **JSON Schema** для схем данных JSON.
#### Посмотрите `openapi.json` { #check-the-openapi-json }
Если вам интересно, как выглядит исходная схема 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>.
Вы увидите JSON, начинающийся примерно так:
```JSON
{
"openapi": "3.1.0",
"info": {
"title": "FastAPI",
"version": "0.1.0"
},
"paths": {
"/items/": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
...
```
#### Для чего нужен OpenAPI { #what-is-openapi-for }
Схема OpenAPI является основой для обеих включённых систем интерактивной документации.
Есть десятки альтернатив, все основаны на OpenAPI. Вы можете легко добавить любую из них в ваше приложение, созданное с **FastAPI**.
Вы также можете использовать её для автоматической генерации кода для клиентов, которые взаимодействуют с вашим API. Например, для фронтенд-, мобильных или IoT-приложений.
## Рассмотрим поэтапно { #recap-step-by-step }
### Шаг 1: импортируйте `FastAPI` { #step-1-import-fastapi }
{* ../../docs_src/first_steps/tutorial001.py hl[1] *}
`FastAPI` — это класс на Python, который предоставляет всю функциональность для вашего API.
/// note | Технические детали
`FastAPI` — это класс, который напрямую наследуется от `Starlette`.
Вы можете использовать весь функционал <a href="https://www.starlette.dev/" class="external-link" target="_blank">Starlette</a> и в `FastAPI`.
///
### Шаг 2: создайте экземпляр `FastAPI` { #step-2-create-a-fastapi-instance }
{* ../../docs_src/first_steps/tutorial001.py hl[3] *}
Здесь переменная `app` будет экземпляром класса `FastAPI`.
Это будет основная точка взаимодействия для создания всего вашего API.
### Шаг 3: создайте *операцию пути (path operation)* { #step-3-create-a-path-operation }
#### Путь (path) { #path }
Здесь «путь» — это последняя часть URL, начиная с первого символа `/`.
Итак, в таком URL:
```
https://example.com/items/foo
```
...путь будет:
```
/items/foo
```
/// info | Информация
«Путь» также часто называют «эндпоинт» или «маршрут».
///
При создании API «путь» — это основной способ разделения «задач» и «ресурсов».
#### Операция (operation) { #operation }
«Операция» здесь — это один из HTTP-«методов».
Один из:
* `POST`
* `GET`
* `PUT`
* `DELETE`
...и более экзотические:
* `OPTIONS`
* `HEAD`
* `PATCH`
* `TRACE`
В протоколе HTTP можно обращаться к каждому пути, используя один (или несколько) из этих «методов».
---
При создании API обычно используют конкретные HTTP-методы для выполнения конкретных действий.
Обычно используют:
* `POST`: создать данные.
* `GET`: прочитать данные.
* `PUT`: обновить данные.
* `DELETE`: удалить данные.
Таким образом, в OpenAPI каждый HTTP-метод называется «операцией».
Мы тоже будем называть их «операциями».
#### Определите *декоратор операции пути (path operation decorator)* { #define-a-path-operation-decorator }
{* ../../docs_src/first_steps/tutorial001.py hl[6] *}
`@app.get("/")` сообщает **FastAPI**, что функция прямо под ним отвечает за обработку запросов, поступающих:
* по пути `/`
* с использованием <abbr title="метод HTTP GET"><code>get</code> операции</abbr>
/// info | Информация о `@decorator`
Синтаксис `@something` в Python называется «декоратор».
Его размещают над функцией. Как красивая декоративная шляпа (кажется, отсюда и пошёл термин).
«Декоратор» берёт функцию ниже и делает с ней что-то.
В нашем случае этот декоратор сообщает **FastAPI**, что функция ниже соответствует **пути** `/` с **операцией** `get`.
Это и есть «декоратор операции пути».
///
Можно также использовать другие операции:
* `@app.post()`
* `@app.put()`
* `@app.delete()`
И более экзотические:
* `@app.options()`
* `@app.head()`
* `@app.patch()`
* `@app.trace()`
/// tip | Подсказка
Вы можете использовать каждый метод (HTTP-операцию) так, как считаете нужным.
**FastAPI** не навязывает какого-либо конкретного смысла.
Эта информация дана как рекомендация, а не требование.
Например, при использовании GraphQL обычно все действия выполняются только с помощью POST-операций.
///
### Шаг 4: определите **функцию операции пути** { #step-4-define-the-path-operation-function }
Вот наша «функция операции пути»:
* **путь**: `/`.
* **операция**: `get`.
* **функция**: функция ниже «декоратора» (ниже `@app.get("/")`).
{* ../../docs_src/first_steps/tutorial001.py hl[7] *}
Это функция на Python.
**FastAPI** будет вызывать её каждый раз, когда получает запрос к URL «`/с операцией `GET`.
В данном случае это асинхронная (`async`) функция.
---
Вы также можете определить её как обычную функцию вместо `async def`:
{* ../../docs_src/first_steps/tutorial003.py hl[7] *}
/// note | Примечание
Если вы не знаете, в чём разница, посмотрите [Асинхронность: *"Нет времени?"*](../async.md#in-a-hurry){.internal-link target=_blank}.
///
### Шаг 5: верните содержимое { #step-5-return-the-content }
{* ../../docs_src/first_steps/tutorial001.py hl[8] *}
Вы можете вернуть `dict`, `list`, отдельные значения `str`, `int` и т.д.
Также можно вернуть модели Pydantic (подробнее об этом позже).
Многие другие объекты и модели будут автоматически преобразованы в JSON (включая ORM и т. п.). Попробуйте использовать те, что вам привычнее, с высокой вероятностью они уже поддерживаются.
## Резюме { #recap }
* Импортируйте `FastAPI`.
* Создайте экземпляр `app`.
* Напишите **декоратор операции пути**, например `@app.get("/")`.
* Определите **функцию операции пути**; например, `def root(): ...`.
* Запустите сервер разработки командой `fastapi dev`.
+255
View File
@@ -0,0 +1,255 @@
# Обработка ошибок { #handling-errors }
Существует множество ситуаций, когда необходимо сообщить об ошибке клиенту, использующему ваш API.
Таким клиентом может быть браузер с фронтендом, чужой код, IoT-устройство и т.д.
Возможно, вам придется сообщить клиенту о следующем:
* Клиент не имеет достаточных привилегий для выполнения данной операции.
* Клиент не имеет доступа к данному ресурсу.
* Элемент, к которому клиент пытался получить доступ, не существует.
* и т.д.
В таких случаях обычно возвращается **HTTP-код статуса ответа** в диапазоне **400** (от 400 до 499).
Они похожи на двухсотые HTTP статус-коды (от 200 до 299), которые означают, что запрос обработан успешно.
Четырёхсотые статус-коды означают, что ошибка произошла по вине клиента.
Помните ли ошибки **"404 Not Found "** (и шутки) ?
## Использование `HTTPException` { #use-httpexception }
Для возврата клиенту HTTP-ответов с ошибками используется `HTTPException`.
### Импортируйте `HTTPException` { #import-httpexception }
{* ../../docs_src/handling_errors/tutorial001.py hl[1] *}
### Вызовите `HTTPException` в своем коде { #raise-an-httpexception-in-your-code }
`HTTPException` - это обычное исключение Python с дополнительными данными, актуальными для API.
Поскольку это исключение Python, то его не `возвращают`, а `вызывают`.
Это также означает, что если вы находитесь внутри функции, которая вызывается внутри вашей *функции операции пути*, и вы поднимаете `HTTPException` внутри этой функции, то она не будет выполнять остальной код в *функции операции пути*, а сразу завершит запрос и отправит HTTP-ошибку из `HTTPException` клиенту.
О том, насколько выгоднее `вызывать` исключение, чем `возвращать` значение, будет рассказано в разделе, посвященном зависимостям и безопасности.
В данном примере, когда клиент запрашивает элемент по несуществующему ID, возникает исключение со статус-кодом `404`:
{* ../../docs_src/handling_errors/tutorial001.py hl[11] *}
### Возвращаемый ответ { #the-resulting-response }
Если клиент запросит `http://example.com/items/foo` (`item_id` `"foo"`), то он получит статус-код 200 и ответ в формате JSON:
```JSON
{
"item": "The Foo Wrestlers"
}
```
Но если клиент запросит `http://example.com/items/bar` (несуществующий `item_id` `"bar"`), то он получит статус-код 404 (ошибка "не найдено") и JSON-ответ в виде:
```JSON
{
"detail": "Item not found"
}
```
/// tip | Подсказка
При вызове `HTTPException` в качестве параметра `detail` можно передавать любое значение, которое может быть преобразовано в JSON, а не только `str`.
Вы можете передать `dict`, `list` и т.д.
Они автоматически обрабатываются **FastAPI** и преобразуются в JSON.
///
## Добавление пользовательских заголовков { #add-custom-headers }
В некоторых ситуациях полезно иметь возможность добавлять пользовательские заголовки к ошибке HTTP. Например, для некоторых типов безопасности.
Скорее всего, вам не потребуется использовать его непосредственно в коде.
Но в случае, если это необходимо для продвинутого сценария, можно добавить пользовательские заголовки:
{* ../../docs_src/handling_errors/tutorial002.py hl[14] *}
## Установка пользовательских обработчиков исключений { #install-custom-exception-handlers }
Вы можете добавить пользовательские обработчики исключений с помощью <a href="https://www.starlette.dev/exceptions/" class="external-link" target="_blank">то же самое исключение - утилиты от Starlette</a>.
Допустим, у вас есть пользовательское исключение `UnicornException`, которое вы (или используемая вами библиотека) можете `вызвать`.
И вы хотите обрабатывать это исключение глобально с помощью FastAPI.
Можно добавить собственный обработчик исключений с помощью `@app.exception_handler()`:
{* ../../docs_src/handling_errors/tutorial003.py hl[5:7,13:18,24] *}
Здесь, если запросить `/unicorns/yolo`, то *операция пути* вызовет `UnicornException`.
Но оно будет обработано `unicorn_exception_handler`.
Таким образом, вы получите чистую ошибку с кодом состояния HTTP `418` и содержимым JSON:
```JSON
{"message": "Oops! yolo did something. There goes a rainbow..."}
```
/// note | Технические детали
Также можно использовать `from starlette.requests import Request` и `from starlette.responses import JSONResponse`.
**FastAPI** предоставляет тот же `starlette.responses`, что и `fastapi.responses`, просто для удобства разработчика. Однако большинство доступных ответов поступает непосредственно из Starlette. То же самое касается и `Request`.
///
## Переопределение стандартных обработчиков исключений { #override-the-default-exception-handlers }
**FastAPI** имеет некоторые обработчики исключений по умолчанию.
Эти обработчики отвечают за возврат стандартных JSON-ответов при `вызове` `HTTPException` и при наличии в запросе недопустимых данных.
Вы можете переопределить эти обработчики исключений на свои собственные.
### Переопределение исключений проверки запроса { #override-request-validation-exceptions }
Когда запрос содержит недопустимые данные, **FastAPI** внутренне вызывает ошибку `RequestValidationError`.
А также включает в себя обработчик исключений по умолчанию.
Чтобы переопределить его, импортируйте `RequestValidationError` и используйте его с `@app.exception_handler(RequestValidationError)` для создания обработчика исключений.
Обработчик исключения получит объект `Request` и исключение.
{* ../../docs_src/handling_errors/tutorial004.py hl[2,14:16] *}
Теперь, если перейти к `/items/foo`, то вместо стандартной JSON-ошибки с:
```JSON
{
"detail": [
{
"loc": [
"path",
"item_id"
],
"msg": "value is not a valid integer",
"type": "type_error.integer"
}
]
}
```
вы получите текстовую версию:
```
1 validation error
path -> item_id
value is not a valid integer (type=type_error.integer)
```
#### `RequestValidationError` или `ValidationError` { #requestvalidationerror-vs-validationerror }
/// warning | Внимание
Это технические детали, которые можно пропустить, если они не важны для вас сейчас.
///
`RequestValidationError` является подклассом Pydantic <a href="https://docs.pydantic.dev/latest/concepts/models/#error-handling" class="external-link" target="_blank">`ValidationError`</a>.
**FastAPI** использует его для того, чтобы, если вы используете Pydantic-модель в `response_model`, и ваши данные содержат ошибку, вы увидели ошибку в журнале.
Но клиент/пользователь этого не увидит. Вместо этого клиент получит сообщение "Internal Server Error" с кодом состояния HTTP `500`.
Так и должно быть, потому что если в вашем *ответе* или где-либо в вашем коде (не в *запросе* клиента) возникает Pydantic `ValidationError`, то это действительно ошибка в вашем коде.
И пока вы не устраните ошибку, ваши клиенты/пользователи не должны иметь доступа к внутренней информации о ней, так как это может привести к уязвимости в системе безопасности.
### Переопределите обработчик ошибок `HTTPException` { #override-the-httpexception-error-handler }
Аналогичным образом можно переопределить обработчик `HTTPException`.
Например, для этих ошибок можно вернуть обычный текстовый ответ вместо JSON:
{* ../../docs_src/handling_errors/tutorial004.py hl[3:4,9:11,22] *}
/// note | Технические детали
Можно также использовать `from starlette.responses import PlainTextResponse`.
**FastAPI** предоставляет тот же `starlette.responses`, что и `fastapi.responses`, просто для удобства разработчика. Однако большинство доступных ответов поступает непосредственно из Starlette.
///
### Используйте тело `RequestValidationError` { #use-the-requestvalidationerror-body }
Ошибка `RequestValidationError` содержит полученное `тело` с недопустимыми данными.
Вы можете использовать его при разработке приложения для регистрации тела и его отладки, возврата пользователю и т.д.
{* ../../docs_src/handling_errors/tutorial005.py hl[14] *}
Теперь попробуйте отправить недействительный элемент, например:
```JSON
{
"title": "towel",
"size": "XL"
}
```
Вы получите ответ о том, что данные недействительны, содержащий следующее тело:
```JSON hl_lines="12-15"
{
"detail": [
{
"loc": [
"body",
"size"
],
"msg": "value is not a valid integer",
"type": "type_error.integer"
}
],
"body": {
"title": "towel",
"size": "XL"
}
}
```
#### `HTTPException` в FastAPI или в Starlette { #fastapis-httpexception-vs-starlettes-httpexception }
**FastAPI** имеет собственный `HTTPException`.
Класс ошибок **FastAPI** `HTTPException` наследует от класса ошибок Starlette `HTTPException`.
Единственное отличие состоит в том, что `HTTPException` в **FastAPI** принимает любые данные, пригодные для преобразования в JSON, в поле `detail`, тогда как `HTTPException` в Starlette принимает для него только строки.
Таким образом, вы можете продолжать вызывать `HTTPException` от **FastAPI** как обычно в своем коде.
Но когда вы регистрируете обработчик исключений, вы должны зарегистрировать его для `HTTPException` от Starlette.
Таким образом, если какая-либо часть внутреннего кодa Starlette, расширение или плагин Starlette вызовет исключение Starlette `HTTPException`, ваш обработчик сможет перехватить и обработать его.
В данном примере, чтобы иметь возможность использовать оба `HTTPException` в одном коде, исключения Starlette переименованы в `StarletteHTTPException`:
```Python
from starlette.exceptions import HTTPException as StarletteHTTPException
```
### Переиспользование обработчиков исключений **FastAPI** { #reuse-fastapis-exception-handlers }
Если вы хотите использовать исключение вместе с теми же обработчиками исключений по умолчанию из **FastAPI**, вы можете импортировать и повторно использовать обработчики исключений по умолчанию из `fastapi.exception_handlers`:
{* ../../docs_src/handling_errors/tutorial006.py hl[2:5,15,21] *}
В этом примере вы просто `выводите в терминал` ошибку с очень выразительным сообщением, но идея вам понятна. Вы можете использовать исключение, а затем просто повторно использовать стандартные обработчики исключений.
@@ -0,0 +1,72 @@
# Модели Header-параметров { #header-parameter-models }
Если у вас есть группа связанных **header-параметров**, то вы можете объединить их в одну **Pydantic-модель**.
Это позволит вам **переиспользовать модель** в **разных местах**, а также задать валидацию и метаданные сразу для всех параметров. 😎
/// note | Заметка
Этот функционал доступен в FastAPI начиная с версии `0.115.0`. 🤓
///
## Header-параметры в виде Pydantic-модели { #header-parameters-with-a-pydantic-model }
Объявите нужные **header-параметры** в **Pydantic-модели** и затем аннотируйте параметр как `Header`:
{* ../../docs_src/header_param_models/tutorial001_an_py310.py hl[9:14,18] *}
**FastAPI** **извлечёт** данные для **каждого поля** из **заголовков** запроса и выдаст заданную вами Pydantic-модель.
## Проверьте документацию { #check-the-docs }
Вы можете посмотреть нужные header-параметры в графическом интерфейсе сгенерированной документации по пути `/docs`:
<div class="screenshot">
<img src="/img/tutorial/header-param-models/image01.png">
</div>
## Как запретить дополнительные заголовки { #forbid-extra-headers }
В некоторых случаях (не особо часто встречающихся) вам может понадобиться **ограничить** заголовки, которые вы хотите получать.
Вы можете использовать возможности конфигурации Pydantic-модели для того, чтобы запретить (`forbid`) любые дополнительные (`extra`) поля:
{* ../../docs_src/header_param_models/tutorial002_an_py310.py hl[10] *}
Если клиент попробует отправить **дополнительные заголовки**, то в ответ он получит **ошибку**.
Например, если клиент попытается отправить заголовок `tool` со значением `plumbus`, то в ответ он получит ошибку, сообщающую ему, что header-параметр `tool` не разрешен:
```json
{
"detail": [
{
"type": "extra_forbidden",
"loc": ["header", "tool"],
"msg": "Extra inputs are not permitted",
"input": "plumbus",
}
]
}
```
## Как отключить автоматическое преобразование подчеркиваний { #disable-convert-underscores }
Как и в случае с обычными заголовками, если у вас в именах параметров имеются символы подчеркивания, они **автоматически преобразовываются в дефис**.
Например, если в коде есть header-параметр `save_data`, то ожидаемый HTTP-заголовок будет `save-data` и именно так он будет отображаться в документации.
Если по каким-то причинам вам нужно отключить данное автоматическое преобразование, это можно сделать и для Pydantic-моделей для header-параметров.
{* ../../docs_src/header_param_models/tutorial003_an_py310.py hl[19] *}
/// warning | Внимание
Перед тем как устанавливать для параметра `convert_underscores` значение `False`, имейте в виду, что некоторые HTTP-прокси и серверы не разрешают использовать заголовки с символами подчеркивания.
///
## Резюме { #summary }
Вы можете использовать **Pydantic-модели** для объявления **header-параметров** в **FastAPI**. 😎
+91
View File
@@ -0,0 +1,91 @@
# Header-параметры { #header-parameters }
Вы можете определить параметры заголовка таким же образом, как вы определяете параметры `Query`, `Path` и `Cookie`.
## Импорт `Header` { #import-header }
Сперва импортируйте `Header`:
{* ../../docs_src/header_params/tutorial001_an_py310.py hl[3] *}
## Объявление параметров `Header` { #declare-header-parameters }
Затем объявите параметры заголовка, используя ту же структуру, что и с `Path`, `Query` и `Cookie`.
Первое значение является значением по умолчанию, вы можете передать все дополнительные параметры валидации или аннотации:
{* ../../docs_src/header_params/tutorial001_an_py310.py hl[9] *}
/// note | Технические детали
`Header` - это "родственный" класс `Path`, `Query` и `Cookie`. Он также наследуется от того же общего класса `Param`.
Но помните, что когда вы импортируете `Query`, `Path`, `Header` и другие из `fastapi`, на самом деле это функции, которые возвращают специальные классы.
///
/// info | Информация
Чтобы объявить заголовки, важно использовать `Header`, иначе параметры интерпретируются как query-параметры.
///
## Автоматическое преобразование { #automatic-conversion }
`Header` обладает небольшой дополнительной функциональностью в дополнение к тому, что предоставляют `Path`, `Query` и `Cookie`.
Большинство стандартных заголовков разделены символом "дефис", также известным как "минус" (`-`).
Но переменная вроде `user-agent` недопустима в Python.
По умолчанию `Header` преобразует символы имен параметров из символа подчеркивания (`_`) в дефис (`-`) для извлечения и документирования заголовков.
Кроме того, HTTP-заголовки не чувствительны к регистру, поэтому вы можете объявить их в стандартном стиле Python (также известном как "snake_case").
Таким образом вы можете использовать `user_agent`, как обычно, в коде Python, вместо того, чтобы вводить заглавные буквы как `User_Agent` или что-то подобное.
Если по какой-либо причине вам необходимо отключить автоматическое преобразование подчеркиваний в дефисы, установите для параметра `convert_underscores` в `Header` значение `False`:
{* ../../docs_src/header_params/tutorial002_an_py310.py hl[10] *}
/// warning | Внимание
Прежде чем установить для `convert_underscores` значение `False`, имейте в виду, что некоторые HTTP-прокси и серверы запрещают использование заголовков с подчеркиванием.
///
## Повторяющиеся заголовки { #duplicate-headers }
Есть возможность получать несколько заголовков с одним и тем же именем, но разными значениями.
Вы можете определить эти случаи, используя список в объявлении типа.
Вы получите все значения из повторяющегося заголовка в виде `list` Python.
Например, чтобы объявить заголовок `X-Token`, который может появляться более одного раза, вы можете написать:
{* ../../docs_src/header_params/tutorial003_an_py310.py hl[9] *}
Если вы взаимодействуете с этой *операцией пути*, отправляя два HTTP-заголовка, таких как:
```
X-Token: foo
X-Token: bar
```
Ответ был бы таким:
```JSON
{
"X-Token values": [
"bar",
"foo"
]
}
```
## Резюме { #recap }
Объявляйте заголовки с помощью `Header`, используя тот же общий шаблон, как при `Query`, `Path` и `Cookie`.
И не беспокойтесь о символах подчеркивания в ваших переменных, **FastAPI** позаботится об их преобразовании.
+95
View File
@@ -0,0 +1,95 @@
# Учебник - Руководство пользователя { #tutorial-user-guide }
В этом руководстве шаг за шагом показано, как использовать **FastAPI** с большинством его функций.
Каждый раздел постепенно основывается на предыдущих, но структура разделяет темы, так что вы можете сразу перейти к нужной теме для решения ваших конкретных задач по API.
Он также создан как справочник на будущее, чтобы вы могли вернуться и посмотреть именно то, что вам нужно.
## Запустите код { #run-the-code }
Все блоки кода можно копировать и использовать напрямую (это действительно протестированные файлы Python).
Чтобы запустить любой из примеров, скопируйте код в файл `main.py` и запустите `fastapi dev` с:
<div class="termy">
```console
$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:solid">main.py</u>
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting development server 🚀
Searching for package file structure from directories
with <font color="#3465A4">__init__.py</font> files
Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
<span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> 🐍 main.py
<span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with
the following code:
<u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u>
<span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000/docs</u></font>
<span style="background-color:#007166"><font color="#D3D7CF"> tip </font></span> Running in development mode, for production use:
<b>fastapi run</b>
Logs:
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Will watch for changes in these directories:
<b>[</b><font color="#4E9A06">&apos;/home/user/code/awesomeapp&apos;</font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font> <b>(</b>Press CTRL+C
to quit<b>)</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started reloader process <b>[</b><font color="#34E2E2"><b>383138</b></font><b>]</b> using WatchFiles
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>383153</b></font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
```
</div>
**НАСТОЯТЕЛЬНО рекомендуется** написать или скопировать код, отредактировать его и запустить локально.
Использование кода в вашем редакторе кода — это то, что действительно показывает преимущества FastAPI: вы увидите, как мало кода нужно написать, все проверки типов, автозавершение и т.д.
---
## Установка FastAPI { #install-fastapi }
Первый шаг — установить FastAPI.
Убедитесь, что вы создали [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активировали его, и затем **установите FastAPI**:
<div class="termy">
```console
$ pip install "fastapi[standard]"
---> 100%
```
</div>
/// note | Примечание
При установке с помощью `pip install "fastapi[standard]"` добавляются некоторые стандартные необязательные зависимости по умолчанию, включая `fastapi-cloud-cli`, который позволяет развернуть приложение на <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>.
Если вы не хотите иметь эти необязательные зависимости, установите просто `pip install fastapi`.
Если вы хотите установить стандартные зависимости, но без `fastapi-cloud-cli`, установите `pip install "fastapi[standard-no-fastapi-cloud-cli]"`.
///
## Продвинутое руководство пользователя { #advanced-user-guide }
Существует также **Продвинутое руководство пользователя**, которое вы сможете прочитать после **Учебник - Руководство пользователя**.
**Продвинутое руководство пользователя** основано на этом, использует те же концепции и обучает некоторым дополнительным функциям.
Но сначала вам следует прочитать **Учебник - Руководство пользователя** (то, что вы читаете прямо сейчас).
Оно спроектировано так, что вы можете создать полноценное приложение, используя только **Учебник - Руководство пользователя**, а затем расширить его различными способами, в зависимости от ваших потребностей, используя дополнительные идеи из **Продвинутого руководства пользователя**.
+120
View File
@@ -0,0 +1,120 @@
# URL-адреса метаданных и документации { #metadata-and-docs-urls }
Вы можете настроить несколько конфигураций метаданных в вашем **FastAPI** приложении.
## Метаданные для 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>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:32] *}
/// tip | Подсказка
Вы можете использовать Markdown в поле `description`, и оно будет отображено в выводе.
///
С этой конфигурацией автоматическая документация 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`.
Он принимает список, содержащий один словарь для каждого тега.
Каждый словарь может содержать в себе:
* `name` (**обязательно**): `str`-значение с тем же именем тега, которое вы используете в параметре `tags` в ваших *операциях пути* и `APIRouter`ах.
* `description`: `str`-значение с кратким описанием для тега. Может содержать Markdown и будет отображаться в UI документации.
* `externalDocs`: `dict`-значение описывающее внешнюю документацию. Включает в себя:
* `description`: `str`-значение с кратким описанием для внешней документации.
* `url` (**обязательно**): `str`-значение с URL-адресом для внешней документации.
### Создание метаданных для тегов { #create-metadata-for-tags }
Давайте попробуем сделать это на примере с тегами для `users` и `items`.
Создайте метаданные для ваших тегов и передайте их в параметре `openapi_tags`:
{* ../../docs_src/metadata/tutorial004.py hl[3:16,18] *}
Помните, что вы можете использовать Markdown внутри описания, к примеру "login" будет отображен жирным шрифтом (**login**) и "fancy" будет отображаться курсивом (_fancy_).
/// tip | Подсказка
Вам необязательно добавлять метаданные для всех используемых тегов
///
### Используйте собственные теги { #use-your-tags }
Используйте параметр `tags` с вашими *операциями пути*`APIRouter`ами), чтобы присвоить им различные теги:
{* ../../docs_src/metadata/tutorial004.py hl[21,26] *}
/// info | Дополнительная информация
Узнайте больше о тегах в [Конфигурации операции пути](path-operation-configuration.md#tags){.internal-link target=_blank}.
///
### Проверьте документацию { #check-the-docs }
Теперь, если вы проверите документацию, вы увидите всю дополнительную информацию:
<img src="/img/tutorial/metadata/image02.png">
### Порядок расположения тегов { #order-of-tags }
Порядок расположения словарей метаданных для каждого тега определяет также порядок, отображаемый в UI документации.
К примеру, несмотря на то, что `users` будут идти после `items` в алфавитном порядке, они отображаются раньше, потому что мы добавляем свои метаданные в качестве первого словаря в списке.
## URL-адрес OpenAPI { #openapi-url }
По умолчанию схема OpenAPI отображена по адресу `/openapi.json`.
Но вы можете изменить это с помощью параметра `openapi_url`.
К примеру, чтобы задать её отображение по адресу `/api/v1/openapi.json`:
{* ../../docs_src/metadata/tutorial002.py hl[3] *}
Если вы хотите отключить схему OpenAPI полностью, вы можете задать `openapi_url=None`, это также отключит пользовательские интерфейсы документации, которые её используют.
## URL-адреса документации { #docs-urls }
Вы можете изменить конфигурацию двух пользовательских интерфейсов документации, которые включены:
* **Swagger UI**: отображаемый по адресу `/docs`.
* Вы можете задать его URL с помощью параметра `docs_url`.
* Вы можете отключить это с помощью настройки `docs_url=None`.
* **ReDoc**: отображаемый по адресу `/redoc`.
* Вы можете задать его URL с помощью параметра `redoc_url`.
* Вы можете отключить это с помощью настройки `redoc_url=None`.
К примеру, чтобы задать отображение Swagger UI по адресу `/documentation` и отключить ReDoc:
{* ../../docs_src/metadata/tutorial003.py hl[3] *}
+97
View File
@@ -0,0 +1,97 @@
# Middleware (Промежуточный слой) { #middleware }
Вы можете добавить промежуточный слой (middleware) в **FastAPI** приложение.
"Middleware" это функция, которая выполняется с каждым запросом до его обработки какой-либо конкретной *операцией пути*.
А также с каждым ответом перед его возвращением.
* Она принимает каждый поступающий **запрос**.
* Может что-то сделать с этим **запросом** или выполнить любой нужный код.
* Затем передает **запрос** для последующей обработки (какой-либо *операцией пути*).
* Получает **ответ** (от *операции пути*).
* Может что-то сделать с этим **ответом** или выполнить любой нужный код.
* И возвращает **ответ**.
/// note | Технические детали
Если у вас есть зависимости с `yield`, то код выхода (код после `yield`) будет выполняться *после* middleware.
Если были какие‑либо фоновые задачи (рассматриваются в разделе [Фоновые задачи](background-tasks.md){.internal-link target=_blank}, вы увидите это позже), они будут запущены *после* всех middleware.
///
## Создание middleware { #create-a-middleware }
Для создания middleware используйте декоратор `@app.middleware("http")`.
Функция middleware получает:
* `request` (объект запроса).
* Функцию `call_next`, которая получает `request` в качестве параметра.
* Эта функция передаёт `request` соответствующей *операции пути*.
* Затем она возвращает ответ `response`, сгенерированный *операцией пути*.
* Также имеется возможность видоизменить `response`, перед тем как его вернуть.
{* ../../docs_src/middleware/tutorial001.py hl[8:9,11,14] *}
/// tip | Примечание
Имейте в виду, что можно добавлять свои собственные заголовки <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" class="external-link" target="_blank">при помощи префикса 'X-'</a>.
Если же вы хотите добавить собственные заголовки, которые клиент сможет увидеть в браузере, то вам потребуется добавить их в настройки CORS ([CORS (Cross-Origin Resource Sharing)](cors.md){.internal-link target=_blank}), используя параметр `expose_headers`, см. документацию <a href="https://www.starlette.dev/middleware/#corsmiddleware" class="external-link" target="_blank">Starlette's CORS docs</a>.
///
/// note | Технические детали
Вы также можете использовать `from starlette.requests import Request`.
**FastAPI** предоставляет такой доступ для удобства разработчиков. Но, на самом деле, это `Request` из Starlette.
///
### До и после `response` { #before-and-after-the-response }
Вы можете добавить код, использующий `request` до передачи его какой-либо *операции пути*.
А также после формирования `response`, до того, как вы его вернёте.
Например, вы можете добавить собственный заголовок `X-Process-Time`, содержащий время в секундах, необходимое для обработки запроса и генерации ответа:
{* ../../docs_src/middleware/tutorial001.py hl[10,12:13] *}
/// tip | Примечание
Мы используем <a href="https://docs.python.org/3/library/time.html#time.perf_counter" class="external-link" target="_blank">`time.perf_counter()`</a> вместо `time.time()` для обеспечения большей точности наших примеров. 🤓
///
## Порядок выполнения нескольких 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.
@@ -0,0 +1,107 @@
# Конфигурация операций пути { #path-operation-configuration }
Существует несколько параметров, которые вы можете передать вашему *декоратору операций пути* для его настройки.
/// warning | Внимание
Помните, что эти параметры передаются непосредственно *декоратору операций пути*, а не вашей *функции-обработчику операций пути*.
///
## Статус-код ответа { #response-status-code }
Вы можете определить (HTTP) `status_code`, который будет использован в ответах вашей *операции пути*.
Вы можете передать только `int`-значение кода, например `404`.
Но если вы не помните, для чего нужен каждый числовой код, вы можете использовать сокращенные константы в параметре `status`:
{* ../../docs_src/path_operation_configuration/tutorial001_py310.py hl[1,15] *}
Этот статус-код будет использован в ответе и будет добавлен в схему OpenAPI.
/// note | Технические детали
Вы также можете использовать `from starlette import status`.
**FastAPI** предоставляет тот же `starlette.status` под псевдонимом `fastapi.status` для удобства разработчика. Но его источник - это непосредственно Starlette.
///
## Теги { #tags }
Вы можете добавлять теги к вашим *операциям пути*, добавив параметр `tags` с `list` заполненным `str`-значениями (обычно в нём только одна строка):
{* ../../docs_src/path_operation_configuration/tutorial002_py310.py hl[15,20,25] *}
Они будут добавлены в схему OpenAPI и будут использованы в автоматической документации интерфейса:
<img src="/img/tutorial/path-operation-configuration/image01.png">
### Теги с перечислениями { #tags-with-enums }
Если у вас большое приложение, вы можете прийти к необходимости добавить **несколько тегов**, и возможно, вы захотите убедиться в том, что всегда используете **один и тот же тег** для связанных *операций пути*.
В этих случаях, имеет смысл хранить теги в классе `Enum`.
**FastAPI** поддерживает это так же, как и в случае с обычными строками:
{* ../../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** прочитает её отсюда.
Вы можете использовать <a href="https://en.wikipedia.org/wiki/Markdown" class="external-link" target="_blank">Markdown</a> в строке документации, и он будет интерпретирован и отображён корректно (с учетом отступа в строке документации).
{* ../../docs_src/path_operation_configuration/tutorial004_py310.py hl[17:25] *}
Он будет использован в интерактивной документации:
<img src="/img/tutorial/path-operation-configuration/image02.png">
## Описание ответа { #response-description }
Вы можете указать описание ответа с помощью параметра `response_description`:
{* ../../docs_src/path_operation_configuration/tutorial005_py310.py hl[19] *}
/// info | Дополнительная информация
Помните, что `response_description` относится конкретно к ответу, а `description` относится к *операции пути* в целом.
///
/// check
OpenAPI указывает, что каждой *операции пути* необходимо описание ответа.
Если вдруг вы не укажете его, то **FastAPI** автоматически сгенерирует это описание с текстом "Successful response".
///
<img src="/img/tutorial/path-operation-configuration/image03.png">
## Обозначение *операции пути* как устаревшей { #deprecate-a-path-operation }
Если вам необходимо пометить *операцию пути* как <abbr title="устаревшее, не рекомендовано к использованию">устаревшую</abbr>, при этом не удаляя её, передайте параметр `deprecated`:
{* ../../docs_src/path_operation_configuration/tutorial006.py hl[16] *}
Он будет четко помечен как устаревший в интерактивной документации:
<img src="/img/tutorial/path-operation-configuration/image04.png">
Проверьте, как будут выглядеть устаревшие и не устаревшие *операции пути*:
<img src="/img/tutorial/path-operation-configuration/image05.png">
## Резюме { #recap }
Вы можете легко конфигурировать и добавлять метаданные в ваши *операции пути*, передавая параметры *декораторам операций пути*.
@@ -0,0 +1,152 @@
# Path-параметры и валидация числовых данных { #path-parameters-and-numeric-validations }
Так же, как с помощью `Query` вы можете добавлять валидацию и метаданные для query-параметров, так и с помощью `Path` вы можете добавлять такую же валидацию и метаданные для path-параметров.
## Импорт `Path` { #import-path }
Сначала импортируйте `Path` из `fastapi`, а также импортируйте `Annotated`:
{* ../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py hl[1,3] *}
/// info | Информация
Поддержка `Annotated` была добавлена в FastAPI начиная с версии 0.95.0 (и с этой версии рекомендуется использовать этот подход).
Если вы используете более старую версию, вы столкнётесь с ошибками при попытке использовать `Annotated`.
Убедитесь, что вы [обновили версию FastAPI](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} как минимум до 0.95.1 перед тем, как использовать `Annotated`.
///
## Определите метаданные { #declare-metadata }
Вы можете указать все те же параметры, что и для `Query`.
Например, чтобы указать значение метаданных `title` для path-параметра `item_id`, вы можете написать:
{* ../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py hl[10] *}
/// note | Примечание
Path-параметр всегда является обязательным, поскольку он должен быть частью пути. Даже если вы объявите его как `None` или зададите значение по умолчанию, это ни на что не повлияет — параметр всё равно будет обязательным.
///
## Задайте нужный вам порядок параметров { #order-the-parameters-as-you-need }
/// tip | Подсказка
Это не имеет большого значения, если вы используете `Annotated`.
///
Допустим, вы хотите объявить query-параметр `q` как обязательный параметр типа `str`.
И если вам больше ничего не нужно указывать для этого параметра, то нет необходимости использовать `Query`.
Но вам по-прежнему нужно использовать `Path` для path-параметра `item_id`. И если по какой-либо причине вы не хотите использовать `Annotated`, то могут возникнуть небольшие сложности.
Если вы поместите параметр со значением по умолчанию перед другим параметром, у которого нет значения по умолчанию, то Python укажет на ошибку.
Но вы можете изменить порядок параметров, чтобы параметр без значения по умолчанию (query-параметр `q`) шёл первым.
Это не имеет значения для **FastAPI**. Он распознает параметры по их названиям, типам и значениям по умолчанию (`Query`, `Path`, и т.д.), ему не важен их порядок.
Поэтому вы можете определить функцию так:
{* ../../docs_src/path_params_numeric_validations/tutorial002.py hl[7] *}
Но имейте в виду, что если вы используете `Annotated`, вы не столкнётесь с этой проблемой, так как вы не используете значения по умолчанию параметров функции для `Query()` или `Path()`.
{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py *}
## Задайте нужный вам порядок параметров, полезные приёмы { #order-the-parameters-as-you-need-tricks }
/// tip | Подсказка
Это не имеет большого значения, если вы используете `Annotated`.
///
Здесь описан **небольшой приём**, который может оказаться удобным, хотя часто он вам не понадобится.
Если вы хотите:
* объявить query-параметр `q` без `Query` и без значения по умолчанию
* объявить path-параметр `item_id` с помощью `Path`
* указать их в другом порядке
* не использовать `Annotated`
...то вы можете использовать специальную возможность синтаксиса Python.
Передайте `*` в качестве первого параметра функции.
Python не будет ничего делать с `*`, но он будет знать, что все следующие параметры являются именованными аргументами (парами ключ-значение), также известными как <abbr title="От: K-ey W-ord Arg-uments"><code>kwargs</code></abbr>, даже если у них нет значений по умолчанию.
{* ../../docs_src/path_params_numeric_validations/tutorial003.py hl[7] *}
### Лучше с `Annotated` { #better-with-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` должен быть целым числом "`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 }
То же самое применимо к:
* `gt`: больше (`g`reater `t`han)
* `le`: меньше или равно (`l`ess than or `e`qual)
{* ../../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`.
Таким образом, `0.5` будет корректным значением. А `0.0` или `0` — нет.
То же самое справедливо и для <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}.
А также вы можете добавить валидацию числовых данных:
* `gt`: больше (`g`reater `t`han)
* `ge`: больше или равно (`g`reater than or `e`qual)
* `lt`: меньше (`l`ess `t`han)
* `le`: меньше или равно (`l`ess than or `e`qual)
/// info | Информация
`Query`, `Path` и другие классы, которые вы разберёте позже, являются наследниками общего класса `Param`.
Все они используют те же параметры для дополнительной валидации и метаданных, которые вы видели ранее.
///
/// note | Технические детали
`Query`, `Path` и другие "классы", которые вы импортируете из `fastapi`, на самом деле являются функциями, которые при вызове возвращают экземпляры одноимённых классов.
Объект `Query`, который вы импортируете, является функцией. И при вызове она возвращает экземпляр одноимённого класса `Query`.
Использование функций (вместо использования классов напрямую) нужно для того, чтобы ваш редактор не подсвечивал ошибки, связанные с их типами.
Таким образом вы можете использовать привычный вам редактор и инструменты разработки, не добавляя дополнительных конфигураций для игнорирования подобных ошибок.
///
+256
View File
@@ -0,0 +1,256 @@
# Path-параметры { #path-parameters }
Вы можете определить "параметры" или "переменные" пути, используя синтаксис форматированных строк Python:
{* ../../docs_src/path_params/tutorial001.py hl[6:7] *}
Значение параметра пути `item_id` будет передано в функцию в качестве аргумента `item_id`.
Если запустите этот пример и перейдёте по адресу: <a href="http://127.0.0.1:8000/items/foo" class="external-link" target="_blank">http://127.0.0.1:8000/items/foo</a>, то увидите ответ:
```JSON
{"item_id":"foo"}
```
## Параметры пути с типами { #path-parameters-with-types }
Вы можете объявить тип параметра пути в функции, используя стандартные аннотации типов Python:
{* ../../docs_src/path_params/tutorial002.py hl[7] *}
Здесь, `item_id` объявлен типом `int`.
/// check | Заметка
Это обеспечит поддержку редактора кода внутри функции (проверка ошибок, автозавершение и т.п.).
///
## <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>, то увидите ответ:
```JSON
{"item_id":3}
```
/// check | Заметка
Обратите внимание на значение `3`, которое получила (и вернула) функция. Это целочисленный Python `int`, а не строка `"3"`.
Используя такое объявление типов, **FastAPI** выполняет автоматический <abbr title="преобразование строк из HTTP-запроса в типы данных Python">"парсинг"</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": [
{
"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`.
Та же ошибка возникнет, если вместо `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** обеспечивает валидацию данных, используя всё те же определения типов.
Обратите внимание, что в тексте ошибки явно указано место, не прошедшее проверку.
Это очень полезно при разработке и отладке кода, который взаимодействует с 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:
<img src="/img/tutorial/path-params/image01.png">
/// check | Заметка
Ещё раз, просто используя определения типов, **FastAPI** обеспечивает автоматическую интерактивную документацию (с интеграцией Swagger UI).
Обратите внимание, что параметр пути объявлен целочисленным.
///
## Преимущества стандартизации, альтернативная документация { #standards-based-benefits-alternative-documentation }
Поскольку сгенерированная схема соответствует стандарту <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>.
<img src="/img/tutorial/path-params/image02.png">
По той же причине, есть множество совместимых инструментов, включая инструменты генерации кода для многих языков.
## Pydantic { #pydantic }
Вся проверка данных выполняется под капотом с помощью <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a>. Поэтому вы можете быть уверены в качестве обработки данных.
Вы можете использовать в аннотациях как простые типы данных, вроде `str`, `float`, `bool`, так и более сложные типы.
Некоторые из них рассматриваются в следующих главах данного руководства.
## Порядок имеет значение { #order-matters }
При создании *операций пути* можно столкнуться с ситуацией, когда путь является фиксированным.
Например, `/users/me`. Предположим, что это путь для получения данных о текущем пользователе.
У вас также может быть путь `/users/{user_id}`, чтобы получить данные о конкретном пользователе по его ID.
Поскольку *операции пути* выполняются в порядке их объявления, необходимо, чтобы путь для `/users/me` был объявлен раньше, чем путь для `/users/{user_id}`:
{* ../../docs_src/path_params/tutorial003.py hl[6,11] *}
Иначе путь для `/users/{user_id}` также будет соответствовать `/users/me`, "подразумевая", что он получает параметр `user_id` со значением `"me"`.
Аналогично, вы не можете переопределить операцию с путем:
{* ../../docs_src/path_params/tutorial003b.py hl[6,11] *}
Первый будет выполняться всегда, так как путь совпадает первым.
## Предопределенные значения { #predefined-values }
Что если нам нужно заранее определить допустимые *параметры пути*, которые *операция пути* может принимать? В таком случае можно использовать стандартное перечисление <abbr title="Enumeration">`Enum`</abbr> Python.
### Создание класса `Enum` { #create-an-enum-class }
Импортируйте `Enum` и создайте подкласс, который наследуется от `str` и `Enum`.
Мы наследуемся от `str`, чтобы документация API могла понять, что значения должны быть типа `string` и отображалась правильно.
Затем создайте атрибуты класса с фиксированными допустимыми значениями:
{* ../../docs_src/path_params/tutorial005.py hl[1,6:9] *}
/// info | Дополнительная информация
<a href="https://docs.python.org/3/library/enum.html" class="external-link" target="_blank">Перечисления (enum) доступны в Python</a> начиная с версии 3.4.
///
/// tip | Подсказка
Если интересно, то "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 { #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`:
{* ../../docs_src/path_params/tutorial005.py hl[20] *}
/// tip | Подсказка
Значение `"lenet"` также можно получить с помощью `ModelName.lenet.value`.
///
#### Возврат *элементов перечисления* { #return-enumeration-members }
Из *операции пути* можно вернуть *элементы перечисления*, даже вложенные в тело JSON (например в `dict`).
Они будут преобразованы в соответствующие значения (в данном случае - строки) перед их возвратом клиенту:
{* ../../docs_src/path_params/tutorial005.py hl[18,21,23] *}
Вы отправите клиенту такой JSON-ответ:
```JSON
{
"model_name": "alexnet",
"message": "Deep Learning FTW!"
}
```
## Path-параметры, содержащие пути { #path-parameters-containing-paths }
Предположим, что есть *операция пути* с путем `/files/{file_path}`.
Но вам нужно, чтобы `file_path` сам содержал *путь*, например, `home/johndoe/myfile.txt`.
Тогда URL для этого файла будет такой: `/files/home/johndoe/myfile.txt`.
### Поддержка OpenAPI { #openapi-support }
OpenAPI не поддерживает способов объявления *параметра пути*, содержащего внутри *путь*, так как это может привести к сценариям, которые сложно определять и тестировать.
Тем не менее это можно сделать в **FastAPI**, используя один из внутренних инструментов Starlette.
Документация по-прежнему будет работать, хотя и не добавит никакой информации о том, что параметр должен содержать путь.
### Конвертер пути { #path-convertor }
Благодаря одной из опций Starlette, можете объявить *параметр пути*, содержащий *путь*, используя URL вроде:
```
/files/{file_path:path}
```
В этом случае `file_path` - это имя параметра, а часть `:path`, указывает, что параметр должен соответствовать любому *пути*.
Можете использовать так:
{* ../../docs_src/path_params/tutorial004.py hl[6] *}
/// tip | Подсказка
Возможно, вам понадобится, чтобы параметр содержал `/home/johndoe/myfile.txt` с ведущим слэшем (`/`).
В этом случае URL будет таким: `/files//home/johndoe/myfile.txt`, с двойным слэшем (`//`) между `files` и `home`.
///
## Резюме { #recap }
Используя **FastAPI** вместе со стандартными объявлениями типов Python (короткими и интуитивно понятными), вы получаете:
* Поддержку редактора кода (проверку ошибок, автозавершение и т.п.)
* "<abbr title="преобразование строк из HTTP-запроса в типы данных Python">Парсинг</abbr>" данных
* Валидацию данных
* Аннотации API и автоматическую документацию
И объявлять типы достаточно один раз.
Это, вероятно, является главным заметным преимуществом **FastAPI** по сравнению с альтернативными фреймворками (кроме <abbr title="не считая оптимизаций">сырой</abbr> производительности).
@@ -0,0 +1,68 @@
# Модели Query-Параметров { #query-parameter-models }
Если у вас есть группа связанных **query-параметров**, то вы можете объединить их в одну **Pydantic-модель**.
Это позволит вам **переиспользовать модель** в **разных местах**, устанавливать валидаторы и метаданные, в том числе для сразу всех параметров, в одном месте. 😎
/// note | Заметка
Этот функционал доступен с версии `0.115.0`. 🤓
///
## Pydantic-Модель для Query-Параметров { #query-parameters-with-a-pydantic-model }
Объявите нужные **query-параметры** в **Pydantic-модели**, а после аннотируйте параметр как `Query`:
{* ../../docs_src/query_param_models/tutorial001_an_py310.py hl[9:13,17] *}
**FastAPI извлечёт** данные соответствующие **каждому полю модели** из **query-параметров** запроса и выдаст вам объявленную Pydantic-модель заполненную ими.
## Проверьте Сгенерированную Документацию { #check-the-docs }
Вы можете посмотреть query-параметры в графическом интерфейсе сгенерированной документации по пути `/docs`:
<div class="screenshot">
<img src="/img/tutorial/query-param-models/image01.png">
</div>
## Запретить Дополнительные Query-Параметры { #forbid-extra-query-parameters }
В некоторых случаях (не особо часто встречающихся) вам может понадобиться **ограничить** query-параметры, которые вы хотите получить.
Вы можете сконфигурировать Pydantic-модель так, чтобы запретить (`forbid`) все дополнительные (`extra`) поля.
{* ../../docs_src/query_param_models/tutorial002_an_py310.py hl[10] *}
Если клиент попробует отправить **дополнительные** данные в **query-параметрах**, то в ответ он получит **ошибку**.
Например, если клиент попытается отправить query-параметр `tool` с значением `plumbus`, в виде:
```http
https://example.com/items/?limit=10&tool=plumbus
```
То в ответ он получит **ошибку**, сообщающую ему, что query-параметр `tool` не разрешен:
```json
{
"detail": [
{
"type": "extra_forbidden",
"loc": ["query", "tool"],
"msg": "Extra inputs are not permitted",
"input": "plumbus"
}
]
}
```
## Заключение { #summary }
Вы можете использовать **Pydantic-модели** для объявления **query-параметров** в **FastAPI**. 😎
/// tip | Совет
Спойлер: вы также можете использовать Pydantic-модели, чтобы объявлять cookies и HTTP-заголовки, но об этом вы прочитаете позже. 🤫
///
@@ -0,0 +1,487 @@
# Query-параметры и валидация строк { #query-parameters-and-string-validations }
**FastAPI** позволяет определять дополнительную информацию и выполнять валидацию для ваших параметров.
Рассмотрим это приложение в качестве примера:
{* ../../docs_src/query_params_str_validations/tutorial001_py310.py hl[7] *}
Query-параметр `q` имеет тип `str | None`, это означает, что он имеет тип `str`, но также может быть `None`. Значение по умолчанию действительно `None`, поэтому FastAPI будет знать, что он не обязателен.
/// note | Технические детали
FastAPI поймёт, что значение `q` не обязательно, из‑за значения по умолчанию `= None`.
Аннотация `str | None` позволит вашему редактору кода обеспечить лучшую поддержку и находить ошибки.
///
## Дополнительная валидация { #additional-validation }
Мы собираемся добавить ограничение: хотя `q` и необязателен, когда он передан, **его длина не должна превышать 50 символов**.
### Импорт `Query` и `Annotated` { #import-query-and-annotated }
Чтобы сделать это, сначала импортируйте:
* `Query` из `fastapi`
* `Annotated` из `typing`
{* ../../docs_src/query_params_str_validations/tutorial002_an_py310.py hl[1,3] *}
/// info | Дополнительная информация
Поддержка `Annotated` (и рекомендация использовать его) появилась в FastAPI версии 0.95.0.
Если у вас более старая версия, при попытке использовать `Annotated` вы получите ошибки.
Убедитесь, что вы [обновили версию FastAPI](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} как минимум до 0.95.1 перед использованием `Annotated`.
///
## Использовать `Annotated` в типе для параметра `q` { #use-annotated-in-the-type-for-the-q-parameter }
Помните, я уже говорил, что `Annotated` можно использовать для добавления метаданных к параметрам в разделе [Введение в типы Python](../python-types.md#type-hints-with-metadata-annotations){.internal-link target=_blank}?
Пришло время использовать его с FastAPI. 🚀
У нас была такая аннотация типа:
//// tab | Python 3.10+
```Python
q: str | None = None
```
////
//// tab | Python 3.8+
```Python
q: Union[str, None] = None
```
////
Мы «обернём» это в `Annotated`, и получится:
//// tab | Python 3.10+
```Python
q: Annotated[str | None] = None
```
////
//// tab | Python 3.8+
```Python
q: Annotated[Union[str, None]] = None
```
////
Обе версии означают одно и то же: `q` — параметр, который может быть `str` или `None`, и по умолчанию равен `None`.
А теперь к самому интересному. 🎉
## Добавим `Query` в `Annotated` для параметра `q` { #add-query-to-annotated-in-the-q-parameter }
Теперь, когда у нас есть `Annotated`, куда можно поместить дополнительную информацию (в нашем случае — дополнительные правила валидации), добавим `Query` внутрь `Annotated` и установим параметр `max_length` равным `50`:
{* ../../docs_src/query_params_str_validations/tutorial002_an_py310.py hl[9] *}
Обратите внимание, что значение по умолчанию по‑прежнему `None`, то есть параметр остаётся необязательным.
Но теперь, добавив `Query(max_length=50)` внутрь `Annotated`, мы говорим FastAPI, что этому значению нужна **дополнительная валидация** — максимум 50 символов. 😎
/// tip | Совет
Здесь мы используем `Query()`, потому что это **query-параметр**. Позже мы увидим другие — `Path()`, `Body()`, `Header()` и `Cookie()`, — они также принимают те же аргументы, что и `Query()`.
///
Теперь 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(default=None)`, это служит той же цели — задать значение по умолчанию (по крайней мере для FastAPI).
Итак:
```Python
q: str | None = Query(default=None)
```
...делает параметр необязательным со значением по умолчанию `None`, так же как:
```Python
q: str | None = None
```
Но вариант с `Query` явно объявляет его как query-параметр.
Затем мы можем передать и другие параметры в `Query`. В данном случае — параметр `max_length`, применимый к строкам:
```Python
q: str | None = Query(default=None, max_length=50)
```
Это провалидирует данные, покажет понятную ошибку, если данные невалидны, и задокументирует параметр в *операции пути* схемы OpenAPI.
### `Query` как значение по умолчанию или внутри `Annotated` { #query-as-the-default-value-or-in-annotated }
Помните, что при использовании `Query` внутри `Annotated` нельзя указывать параметр `default` у `Query`.
Вместо этого используйте обычное значение по умолчанию параметра функции. Иначе это будет неоднозначно.
Например, так делать нельзя:
```Python
q: Annotated[str, Query(default="rick")] = "morty"
```
...потому что непонятно, какое значение должно быть по умолчанию: `"rick"` или `"morty"`.
Следовательно, используйте (предпочтительно):
```Python
q: Annotated[str, Query()] = "rick"
```
...или в старой кодовой базе вы увидите:
```Python
q: str = Query(default="rick")
```
### Преимущества `Annotated` { #advantages-of-annotated }
**Рекомендуется использовать `Annotated`** вместо задания значения по умолчанию в параметрах функции — так **лучше** по нескольким причинам. 🤓
**Значение по умолчанию** у **параметра функции** — это **настоящее значение по умолчанию**, что более интуитивно для Python. 😌
Вы можете **вызвать** эту же функцию в **других местах** без FastAPI, и она будет **работать как ожидается**. Если есть **обязательный** параметр (без значения по умолчанию), ваш **редактор кода** сообщит об ошибке, **Python** тоже пожалуется, если вы запустите её без передачи обязательного параметра.
Если вы не используете `Annotated`, а применяете **(устаревший) стиль со значением по умолчанию**, то при вызове этой функции без FastAPI в **других местах** вам нужно **помнить** о том, что надо передать аргументы, чтобы всё работало корректно, иначе значения будут не такими, как вы ожидаете (например, вместо `str` будет `QueryInfo` или что-то подобное). И ни редактор, ни Python не будут ругаться при самом вызове функции — ошибка проявится лишь при операциях внутри.
Так как `Annotated` может содержать больше одной аннотации метаданных, теперь вы можете использовать ту же функцию и с другими инструментами, например с <a href="https://typer.tiangolo.com/" class="external-link" target="_blank">Typer</a>. 🚀
## Больше валидаций { #add-more-validations }
Можно также добавить параметр `min_length`:
{* ../../docs_src/query_params_str_validations/tutorial003_an_py310.py hl[10] *}
## Регулярные выражения { #add-regular-expressions }
Вы можете определить <abbr title="Регулярное выражение (regex, regexp) — это последовательность символов, задающая шаблон поиска для строк.">регулярное выражение</abbr> `pattern`, которому должен соответствовать параметр:
{* ../../docs_src/query_params_str_validations/tutorial004_an_py310.py hl[11] *}
Данный шаблон регулярного выражения проверяет, что полученное значение параметра:
* `^`: начинается с следующих символов, до них нет символов.
* `fixedquery`: имеет точное значение `fixedquery`.
* `$`: заканчивается здесь, после `fixedquery` нет никаких символов.
Если вы теряетесь во всех этих идеях про **«регулярные выражения»**, не переживайте. Это сложная тема для многих. Многое можно сделать и без них.
Теперь вы знаете, что когда они понадобятся, вы сможете использовать их в **FastAPI**.
### `regex` из Pydantic v1 вместо `pattern` { #pydantic-v1-regex-instead-of-pattern }
До Pydantic версии 2 и до FastAPI 0.100.0 этот параметр назывался `regex`, а не `pattern`, но сейчас он устарел.
Вы всё ещё можете встретить такой код:
//// 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 | Примечание
Наличие значения по умолчанию любого типа, включая `None`, делает параметр необязательным.
///
## Обязательные параметры { #required-parameters }
Когда не требуется объявлять дополнительные проверки или метаданные, можно сделать query-параметр `q` обязательным, просто не указывая значение по умолчанию, например:
```Python
q: str
```
вместо:
```Python
q: str | None = None
```
Но сейчас мы объявляем его через `Query`, например так:
```Python
q: Annotated[str | None, Query(min_length=3)] = None
```
Поэтому, когда вам нужно объявить значение как обязательное при использовании `Query`, просто не указывайте значение по умолчанию:
{* ../../docs_src/query_params_str_validations/tutorial006_an_py39.py hl[9] *}
### Обязательный, но может быть `None` { #required-can-be-none }
Можно объявить, что параметр может принимать `None`, но при этом остаётся обязательным. Это заставит клиентов отправлять значение, даже если это значение — `None`.
Для этого объявите, что `None` — валидный тип, но просто не задавайте значение по умолчанию:
{* ../../docs_src/query_params_str_validations/tutorial006c_an_py310.py hl[9] *}
## Query-параметр - список / несколько значений { #query-parameter-list-multiple-values }
Когда вы явно объявляете query-параметр через `Query`, можно также указать, что он принимает список значений, иначе говоря — несколько значений.
Например, чтобы объявить query-параметр `q`, который может встречаться в URL несколько раз, можно написать:
{* ../../docs_src/query_params_str_validations/tutorial011_an_py310.py hl[9] *}
Тогда при таком URL:
```
http://localhost:8000/items/?q=foo&q=bar
```
вы получите множественные значения query-параметра `q` (`foo` и `bar`) в виде Python-`list` внутри вашей *функции обработки пути*, в *параметре функции* `q`.
Таким образом, ответ на этот URL будет:
```JSON
{
"q": [
"foo",
"bar"
]
}
```
/// tip | Совет
Чтобы объявить query-параметр типа `list`, как в примере выше, нужно явно использовать `Query`, иначе он будет интерпретирован как тело запроса.
///
Интерактивная документация API обновится соответствующим образом и позволит передавать несколько значений:
<img src="/img/tutorial/query-params-str-validations/image02.png">
### Query-параметр - список / несколько значений со значением по умолчанию { #query-parameter-list-multiple-values-with-defaults }
Можно также определить значение по умолчанию как `list`, если ничего не передано:
{* ../../docs_src/query_params_str_validations/tutorial012_an_py39.py hl[9] *}
Если вы перейдёте по адресу:
```
http://localhost:8000/items/
```
значение по умолчанию для `q` будет: `["foo", "bar"]`, и ответом будет:
```JSON
{
"q": [
"foo",
"bar"
]
}
```
#### Просто `list` { #using-just-list }
Можно использовать `list` напрямую вместо `list[str]`:
{* ../../docs_src/query_params_str_validations/tutorial013_an_py39.py hl[9] *}
/// note | Технические детали
Имейте в виду, что в этом случае FastAPI не будет проверять содержимое списка.
Например, `list[int]` проверит (и задокументирует), что элементы списка — целые числа. А просто `list` — нет.
///
## Больше метаданных { #declare-more-metadata }
Можно добавить больше информации о параметре.
Эта информация будет включена в сгенерированную OpenAPI-схему и использована интерфейсами документации и внешними инструментами.
/// note | Технические детали
Помните, что разные инструменты могут иметь разный уровень поддержки OpenAPI.
Некоторые из них пока могут не показывать всю дополнительную информацию, хотя в большинстве случаев недостающая возможность уже запланирована к разработке.
///
Можно задать `title`:
{* ../../docs_src/query_params_str_validations/tutorial007_an_py310.py hl[10] *}
И `description`:
{* ../../docs_src/query_params_str_validations/tutorial008_an_py310.py hl[14] *}
## Псевдонимы параметров { #alias-parameters }
Представьте, что вы хотите, чтобы параметр назывался `item-query`.
Например:
```
http://127.0.0.1:8000/items/?item-query=foobaritems
```
Но `item-query` — недопустимое имя переменной в Python.
Ближайший вариант — `item_query`.
Но вам всё равно нужно именно `item-query`...
Тогда можно объявить `alias`, и этот псевдоним будет использован для поиска значения параметра:
{* ../../docs_src/query_params_str_validations/tutorial009_an_py310.py hl[9] *}
## Маркировка параметров как устаревших { #deprecating-parameters }
Предположим, этот параметр вам больше не нравится.
Его нужно оставить на какое‑то время, так как клиенты его используют, но вы хотите, чтобы в документации он явно отображался как <abbr title="устаревший, не рекомендуется использовать">устаревший</abbr>.
Тогда передайте параметр `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 { #exclude-parameters-from-openapi }
Чтобы исключить 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`
Проверки, специфичные для строк:
* `min_length`
* `max_length`
* `pattern`
Кастомные проверки с использованием `AfterValidator`.
В этих примерах вы видели, как объявлять проверки для значений типа `str`.
Смотрите следующие главы, чтобы узнать, как объявлять проверки для других типов, например чисел.
+187
View File
@@ -0,0 +1,187 @@
# Query-параметры { #query-parameters }
Когда вы объявляете параметры функции, которые не являются параметрами пути, они автоматически интерпретируются как "query"-параметры.
{* ../../docs_src/query_params/tutorial001.py hl[9] *}
Query-параметры представляют из себя набор пар ключ-значение, которые идут после знака `?` в URL-адресе, разделенные символами `&`.
Например, в этом URL-адресе:
```
http://127.0.0.1:8000/items/?skip=0&limit=10
```
...параметры запроса такие:
* `skip`: со значением `0`
* `limit`: со значением `10`
Будучи частью URL-адреса, они "по умолчанию" являются строками.
Но когда вы объявляете их с использованием типов Python (в примере выше, как `int`), они конвертируются в указанный тип данных и проходят проверку на соответствие ему.
Все те же правила, которые применяются к path-параметрам, также применяются и query-параметрам:
* Поддержка от редактора кода (очевидно)
* <abbr title="преобразование строки, полученной из HTTP запроса в Python данные">"Парсинг"</abbr> данных
* Проверка на соответствие данных (Валидация)
* Автоматическая документация
## Значения по умолчанию { #defaults }
Поскольку query-параметры не являются фиксированной частью пути, они могут быть не обязательными и иметь значения по умолчанию.
В примере выше значения по умолчанию равны `skip=0` и `limit=10`.
Таким образом, результат перехода по URL-адресу:
```
http://127.0.0.1:8000/items/
```
будет таким же, как если перейти используя параметры по умолчанию:
```
http://127.0.0.1:8000/items/?skip=0&limit=10
```
Но если вы введёте, например:
```
http://127.0.0.1:8000/items/?skip=20
```
Значения параметров в вашей функции будут:
* `skip=20`: потому что вы установили это в URL-адресе
* `limit=10`: т.к это было значение по умолчанию
## Необязательные параметры { #optional-parameters }
Аналогично, вы можете объявлять необязательные query-параметры, установив их значение по умолчанию, равное `None`:
{* ../../docs_src/query_params/tutorial002_py310.py hl[7] *}
В этом случае, параметр `q` будет не обязательным и будет иметь значение `None` по умолчанию.
/// check | Важно
Также обратите внимание, что **FastAPI** достаточно умён чтобы заметить, что параметр `item_id` является path-параметром, а `q` нет, поэтому, это параметр запроса.
///
## Преобразование типа параметра запроса { #query-parameter-type-conversion }
Вы также можете объявлять параметры с типом `bool`, которые будут преобразованы соответственно:
{* ../../docs_src/query_params/tutorial003_py310.py hl[7] *}
В этом случае, если вы сделаете запрос:
```
http://127.0.0.1:8000/items/foo?short=1
```
или
```
http://127.0.0.1:8000/items/foo?short=True
```
или
```
http://127.0.0.1:8000/items/foo?short=true
```
или
```
http://127.0.0.1:8000/items/foo?short=on
```
или
```
http://127.0.0.1:8000/items/foo?short=yes
```
или в любом другом варианте написания (в верхнем регистре, с заглавной буквой, и т.п), внутри вашей функции параметр `short` будет иметь значение `True` типа данных `bool` . В противном случае - `False`.
## Смешивание query-параметров и path-параметров { #multiple-path-and-query-parameters }
Вы можете объявлять несколько query-параметров и path-параметров одновременно, **FastAPI** сам разберётся, что чем является.
И вы не обязаны объявлять их в каком-либо определенном порядке.
Они будут обнаружены по именам:
{* ../../docs_src/query_params/tutorial004_py310.py hl[6,8] *}
## Обязательные query-параметры { #required-query-parameters }
Когда вы объявляете значение по умолчанию для параметра, который не является path-параметром (в этом разделе, мы пока что познакомились только с path-параметрами), то он не является обязательным.
Если вы не хотите задавать конкретное значение, но хотите сделать параметр необязательным, вы можете установить значение по умолчанию равным `None`.
Но если вы хотите сделать query-параметр обязательным, вы можете просто не указывать значение по умолчанию:
{* ../../docs_src/query_params/tutorial005.py hl[6:7] *}
Здесь параметр запроса `needy` является обязательным параметром с типом данных `str`.
Если вы откроете в браузере URL-адрес, например:
```
http://127.0.0.1:8000/items/foo-item
```
...без добавления обязательного параметра `needy`, вы увидите подобного рода ошибку:
```JSON
{
"detail": [
{
"type": "missing",
"loc": [
"query",
"needy"
],
"msg": "Field required",
"input": null
}
]
}
```
Поскольку `needy` является обязательным параметром, вам необходимо указать его в URL-адресе:
```
http://127.0.0.1:8000/items/foo-item?needy=sooooneedy
```
...это будет работать:
```JSON
{
"item_id": "foo-item",
"needy": "sooooneedy"
}
```
Конечно, вы можете определить некоторые параметры как обязательные, некоторые — со значением по умолчанию, а некоторые — полностью необязательные:
{* ../../docs_src/query_params/tutorial006_py310.py hl[8] *}
В этом примере, у нас есть 3 параметра запроса:
* `needy`, обязательный `str`.
* `skip`, типа `int` и со значением по умолчанию `0`.
* `limit`, необязательный `int`.
/// tip | Подсказка
Вы можете использовать класс `Enum` также, как ранее применяли его с [Path-параметрами](path-params.md#predefined-values){.internal-link target=_blank}.
///
+177
View File
@@ -0,0 +1,177 @@
# Загрузка файлов { #request-files }
Используя класс `File`, мы можем позволить клиентам загружать файлы.
/// info | Дополнительная информация
Чтобы получать загруженные файлы, сначала установите <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>.
Убедитесь, что вы создали [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активировали его, а затем установили пакет, например:
```console
$ pip install python-multipart
```
Это связано с тем, что загружаемые файлы передаются как "данные формы".
///
## Импорт `File` { #import-file }
Импортируйте `File` и `UploadFile` из модуля `fastapi`:
{* ../../docs_src/request_files/tutorial001_an_py39.py hl[3] *}
## Определите параметры `File` { #define-file-parameters }
Создайте параметры `File` так же, как вы это делаете для `Body` или `Form`:
{* ../../docs_src/request_files/tutorial001_an_py39.py hl[9] *}
/// info | Дополнительная информация
`File` - это класс, который наследуется непосредственно от `Form`.
Но помните, что когда вы импортируете `Query`, `Path`, `File` и другие из `fastapi`, на самом деле это функции, которые возвращают специальные классы.
///
/// tip | Подсказка
Для объявления тела файла необходимо использовать `File`, поскольку в противном случае параметры будут интерпретироваться как параметры запроса или параметры тела (JSON).
///
Файлы будут загружены как данные формы.
Если вы объявите тип параметра у *функции операции пути* как `bytes`, то **FastAPI** прочитает файл за вас, и вы получите его содержимое в виде `bytes`.
Следует иметь в виду, что все содержимое будет храниться в памяти. Это хорошо подходит для небольших файлов.
Однако возможны случаи, когда использование `UploadFile` может оказаться полезным.
## Параметры файла с `UploadFile` { #file-parameters-with-uploadfile }
Определите параметр файла с типом `UploadFile`:
{* ../../docs_src/request_files/tutorial001_an_py39.py hl[14] *}
Использование `UploadFile` имеет ряд преимуществ перед `bytes`:
* Использовать `File()` в значении параметра по умолчанию не обязательно.
* При этом используется "буферный" файл:
* Файл, хранящийся в памяти до максимального предела размера, после преодоления которого он будет храниться на диске.
* Это означает, что он будет хорошо работать с большими файлами, такими как изображения, видео, большие бинарные файлы и т.д., не потребляя при этом всю память.
* Из загруженного файла можно получить метаданные.
* Он реализует <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` имеет следующие атрибуты:
* `filename`: Строка `str` с исходным именем файла, который был загружен (например, `myimage.jpg`).
* `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`).
* `write(data)`: Записать данные `data` (`str` или `bytes`) в файл.
* `read(size)`: Прочитать количество `size` (`int`) байт/символов из файла.
* `seek(offset)`: Перейти к байту на позиции `offset` (`int`) в файле.
* Например, `await myfile.seek(0)` перейдет к началу файла.
* Это особенно удобно, если вы один раз выполнили команду `await myfile.read()`, а затем вам нужно прочитать содержимое файла еще раз.
* `close()`: Закрыть файл.
Поскольку все эти методы являются `async` методами, вам следует использовать "await" вместе с ними.
Например, внутри `async` *функции операции пути* можно получить содержимое с помощью:
```Python
contents = await myfile.read()
```
Если вы находитесь внутри обычной `def` *функции операции пути*, можно получить прямой доступ к файлу `UploadFile.file`, например:
```Python
contents = myfile.file.read()
```
/// note | Технические детали `async`
При использовании методов `async` **FastAPI** запускает файловые методы в пуле потоков и ожидает их.
///
/// note | Технические детали Starlette
**FastAPI** наследует `UploadFile` непосредственно из **Starlette**, но добавляет некоторые детали для совместимости с **Pydantic** и другими частями FastAPI.
///
## Что такое «данные формы» { #what-is-form-data }
Способ, которым HTML-формы (`<form></form>`) отправляют данные на сервер, обычно использует "специальную" кодировку для этих данных, отличную от JSON.
**FastAPI** позаботится о том, чтобы считать эти данные из нужного места, а не из JSON.
/// note | Технические детали
Данные из форм обычно кодируются с использованием "media type" `application/x-www-form-urlencoded` когда он не включает файлы.
Но когда форма включает файлы, она кодируется как 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 – Сеть разработчиков Mozilla">MDN</abbr> web docs for <code>POST</code></a>.
///
/// warning | Внимание
В операции *функции операции пути* можно объявить несколько параметров `File` и `Form`, но нельзя также объявлять поля `Body`, которые предполагается получить в виде JSON, поскольку тело запроса будет закодировано с помощью `multipart/form-data`, а не `application/json`.
Это не является ограничением **FastAPI**, это часть протокола HTTP.
///
## Необязательная загрузка файлов { #optional-file-upload }
Вы можете сделать загрузку файла необязательной, используя стандартные аннотации типов и установив значение по умолчанию `None`:
{* ../../docs_src/request_files/tutorial001_02_an_py310.py hl[9,17] *}
## `UploadFile` с дополнительными метаданными { #uploadfile-with-additional-metadata }
Вы также можете использовать `File()` вместе с `UploadFile`, например, для установки дополнительных метаданных:
{* ../../docs_src/request_files/tutorial001_03_an_py39.py hl[9,15] *}
## Загрузка нескольких файлов { #multiple-file-uploads }
Можно одновременно загружать несколько файлов.
Они будут связаны с одним и тем же "полем формы", отправляемым с помощью данных формы.
Для этого необходимо объявить список `bytes` или `UploadFile`:
{* ../../docs_src/request_files/tutorial002_an_py39.py hl[10,15] *}
Вы получите, как и было объявлено, список `list` из `bytes` или `UploadFile`.
/// note | Технические детали
Можно также использовать `from starlette.responses import HTMLResponse`.
**FastAPI** предоставляет тот же `starlette.responses`, что и `fastapi.responses`, просто для удобства разработчика. Однако большинство доступных ответов поступает непосредственно из Starlette.
///
### Загрузка нескольких файлов с дополнительными метаданными { #multiple-file-uploads-with-additional-metadata }
Так же, как и раньше, вы можете использовать `File()` для задания дополнительных параметров, даже для `UploadFile`:
{* ../../docs_src/request_files/tutorial003_an_py39.py hl[11,18:20] *}
## Резюме { #recap }
Используйте `File`, `bytes` и `UploadFile` для работы с файлами, которые будут загружаться и передаваться в виде данных формы.
@@ -0,0 +1,78 @@
# Модели форм { #form-models }
Вы можете использовать **Pydantic-модели** для объявления **полей формы** в FastAPI.
/// info | Дополнительная информация
Чтобы использовать формы, сначала установите <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>.
Убедитесь, что вы создали и активировали [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, а затем установите пакет, например:
```console
$ pip install python-multipart
```
///
/// note | Заметка
Этот функционал доступен начиная с версии FastAPI `0.113.0`. 🤓
///
## Pydantic-модели для форм { #pydantic-models-for-forms }
Вам просто нужно объявить **Pydantic-модель** с полями, которые вы хотите получить как **поля формы**, а затем объявить параметр как `Form`:
{* ../../docs_src/request_form_models/tutorial001_an_py39.py hl[9:11,15] *}
**FastAPI** **извлечёт** данные для **каждого поля** из **данных формы** в запросе и выдаст вам объявленную Pydantic-модель.
## Проверьте документацию { #check-the-docs }
Вы можете проверить это в интерфейсе документации по адресу `/docs`:
<div class="screenshot">
<img src="/img/tutorial/request-form-models/image01.png">
</div>
## Запрет дополнительных полей формы { #forbid-extra-form-fields }
В некоторых случаях (не особо часто встречающихся) вам может понадобиться **ограничить** поля формы только теми, которые объявлены в Pydantic-модели. И **запретить** любые **дополнительные** поля.
/// note | Заметка
Этот функционал доступен начиная с версии FastAPI `0.114.0`. 🤓
///
Вы можете сконфигурировать Pydantic-модель так, чтобы запретить (`forbid`) все дополнительные (`extra`) поля:
{* ../../docs_src/request_form_models/tutorial002_an_py39.py hl[12] *}
Если клиент попробует отправить дополнительные данные, то в ответ он получит **ошибку**.
Например, если клиент попытается отправить поля формы:
* `username`: `Rick`
* `password`: `Portal Gun`
* `extra`: `Mr. Poopybutthole`
То в ответ он получит **ошибку**, сообщающую ему, что поле `extra` не разрешено:
```json
{
"detail": [
{
"type": "extra_forbidden",
"loc": ["body", "extra"],
"msg": "Extra inputs are not permitted",
"input": "Mr. Poopybutthole"
}
]
}
```
## Итоги { #summary }
Вы можете использовать Pydantic-модели для объявления полей форм в FastAPI. 😎
@@ -0,0 +1,41 @@
# Файлы и формы в запросе { #request-forms-and-files }
Вы можете определять файлы и поля формы одновременно, используя `File` и `Form`.
/// info | Информация
Чтобы получать загруженные файлы и/или данные форм, сначала установите <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>.
Убедитесь, что вы создали [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активировали его, а затем установили пакет, например:
```console
$ pip install python-multipart
```
///
## Импортируйте `File` и `Form` { #import-file-and-form }
{* ../../docs_src/request_forms_and_files/tutorial001_an_py39.py hl[3] *}
## Определите параметры `File` и `Form` { #define-file-and-form-parameters }
Создайте параметры файла и формы таким же образом, как для `Body` или `Query`:
{* ../../docs_src/request_forms_and_files/tutorial001_an_py39.py hl[10:12] *}
Файлы и поля формы будут загружены в виде данных формы, и вы получите файлы и поля формы.
Вы можете объявить некоторые файлы как `bytes`, а некоторые — как `UploadFile`.
/// warning | Внимание
Вы можете объявить несколько параметров `File` и `Form` в операции пути, но вы не можете также объявить поля `Body`, которые вы ожидаете получить в виде JSON, так как запрос будет иметь тело, закодированное с помощью `multipart/form-data` вместо `application/json`.
Это не ограничение **FastAPI**, это часть протокола HTTP.
///
## Резюме { #recap }
Используйте `File` и `Form` вместе, когда необходимо получить данные и файлы в одном запросе.

Some files were not shown because too many files have changed in this diff Show More