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
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:
@@ -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`.
|
||||
|
||||
////
|
||||
@@ -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`.
|
||||
|
||||
Например, вместо использования той же сессии базы данных, создайте новую сессию в фоновой задаче и получите объекты из базы данных с помощью этой новой сессии. И затем, вместо передачи объекта из базы данных параметром в функцию фоновой задачи, передавайте идентификатор этого объекта и заново получайте объект внутри функции фоновой задачи.
|
||||
@@ -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.
|
||||
|
||||
///
|
||||
@@ -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` внутри, так что всё просто работает. ✨
|
||||
@@ -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}.
|
||||
@@ -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`. 🔖
|
||||
@@ -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}.
|
||||
@@ -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‑параметрах и т.д.
|
||||
* Данных ответа.
|
||||
|
||||
У вас также будут **ошибки прямо в редакторе** для всего.
|
||||
|
||||
И каждый раз, когда вы обновляете код бэкенда и **перегенерируете** фронтенд, в нём появятся новые *операции пути* как методы, старые будут удалены, а любые другие изменения отразятся в сгенерированном коде. 🤓
|
||||
|
||||
Это также означает, что если что‑то изменилось, это будет **отражено** в клиентском коде автоматически. И если вы **соберёте** клиент, он завершится с ошибкой, если где‑то есть **несоответствие** в используемых данных.
|
||||
|
||||
Таким образом, вы **обнаружите многие ошибки** очень рано в цикле разработки, вместо того чтобы ждать, когда ошибки проявятся у конечных пользователей в продакшн, и затем пытаться отладить, в чём проблема. ✨
|
||||
@@ -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}.
|
||||
|
||||
И следующие разделы предполагают, что вы уже прочитали его, и предполагают, что вы знаете эти основные идеи.
|
||||
@@ -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>.
|
||||
@@ -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">
|
||||
@@ -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` в зависимостях и установить в них статус-код. Но помните, что последнее установленное значение будет иметь приоритет.
|
||||
@@ -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`, при этом сохраняя автоматическое преобразование данных, документацию и т.д.
|
||||
@@ -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] *}
|
||||
@@ -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`.
|
||||
@@ -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 снова и снова для каждого запроса, при этом давая возможность переопределять его во время тестирования.
|
||||
@@ -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}.
|
||||
@@ -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 | Совет
|
||||
|
||||
Если вы хотите переопределять зависимость только во время некоторых тестов, задайте переопределение в начале теста (внутри функции теста) и сбросьте его в конце (в конце функции теста).
|
||||
|
||||
///
|
||||
@@ -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.
|
||||
|
||||
///
|
||||
@@ -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>.
|
||||
@@ -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"
|
||||
}
|
||||
```
|
||||
@@ -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}.
|
||||
@@ -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>.
|
||||
@@ -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 и им подобные. Фреймворки с интегрированной автоматической валидацией данных, сериализацией и документацией.
|
||||
@@ -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>
|
||||
@@ -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
|
||||
* Запуск при старте
|
||||
* Перезапуски
|
||||
* Репликация (количество запущенных процессов)
|
||||
* Память
|
||||
* Предварительные шаги перед запуском
|
||||
|
||||
Понимание этих идей и того, как их применять, даст вам интуицию, необходимую для принятия решений при настройке и доработке ваших развёртываний. 🤓
|
||||
|
||||
В следующих разделах я приведу более конкретные примеры возможных стратегий. 🚀
|
||||
@@ -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>):
|
||||
|
||||

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

|
||||
|
||||
## Собрать 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**, вы можете **минимизировать время сборки**, чтобы повысить продуктивность (и не скучать). 😎
|
||||
@@ -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**. 🔒
|
||||
@@ -0,0 +1,21 @@
|
||||
# Развёртывание { #deployment }
|
||||
|
||||
Развернуть приложение **FastAPI** довольно просто.
|
||||
|
||||
## Что означает развёртывание { #what-does-deployment-mean }
|
||||
|
||||
Термин **развёртывание** (приложения) означает выполнение необходимых шагов, чтобы сделать приложение **доступным для пользователей**.
|
||||
|
||||
Для **веб-API** это обычно означает размещение его на **удалённой машине** с **серверной программой**, обеспечивающей хорошую производительность, стабильность и т.д., чтобы ваши **пользователи** могли **получать доступ** к приложению эффективно и без перебоев или проблем.
|
||||
|
||||
Это отличается от этапов **разработки**, когда вы постоянно меняете код, ломаете его и исправляете, останавливаете и перезапускаете сервер разработки и т.д.
|
||||
|
||||
## Стратегии развёртывания { #deployment-strategies }
|
||||
|
||||
В зависимости от вашего конкретного случая, есть несколько способов сделать это.
|
||||
|
||||
Вы можете **развернуть сервер** самостоятельно, используя различные инструменты. Например, можно использовать **облачный сервис**, который выполнит часть работы за вас. Также возможны и другие варианты.
|
||||
|
||||
В этом блоке я покажу вам некоторые из основных концепций, которые вы, вероятно, должны иметь в виду при развертывании приложения **FastAPI** (хотя большинство из них применимо к любому другому типу веб-приложений).
|
||||
|
||||
В последующих разделах вы узнаете больше деталей и методов, необходимых для этого. ✨
|
||||
@@ -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
|
||||
* Запуск при старте системы
|
||||
* Перезапуски
|
||||
* Репликация (количество запущенных процессов)
|
||||
* Память
|
||||
* Предварительные шаги перед запуском
|
||||
|
||||
В следующих главах я расскажу подробнее про каждую из этих концепций, о том, как о них думать, и приведу конкретные примеры со стратегиями, как с ними работать. 🚀
|
||||
@@ -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). Вы увидите, что эти инструменты тоже предлагают простые способы решить другие **концепции деплоя**. ✨
|
||||
@@ -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
|
||||
```
|
||||
@@ -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).
|
||||
@@ -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">'/home/user/code/awesomeapp'</font><b>]</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font> <b>(</b>Press CTRL+C to
|
||||
quit<b>)</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started reloader process <b>[</b><font color="#34E2E2"><b>383138</b></font><b>]</b> using WatchFiles
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>383153</b></font><b>]</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
Приложение командной строки `fastapi` это и есть **FastAPI 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}.
|
||||
|
||||
///
|
||||
@@ -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 прямо из браузера.
|
||||
|
||||

|
||||
|
||||
* Альтернативная документация API в <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank"><strong>ReDoc</strong></a>.
|
||||
|
||||

|
||||
|
||||
### Только современный 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>:
|
||||
|
||||

|
||||
|
||||
* в <a href="https://www.jetbrains.com/pycharm/" class="external-link" target="_blank">PyCharm</a>:
|
||||
|
||||

|
||||
|
||||
Вы будете получать автозавершение кода даже там, где вы считали это невозможным раньше. Как пример, ключ `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% покрытие тестами.
|
||||
@@ -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>. В зависимости от уровня поддержки Вы можете получить дополнительные бонусы, например, бейдж в документации. 🎁
|
||||
|
||||
---
|
||||
|
||||
Спасибо! 🚀
|
||||
@@ -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] *}
|
||||
@@ -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">
|
||||
@@ -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}.
|
||||
@@ -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>.
|
||||
|
||||
Также можно почитать больше о каждой из указанных выше библиотек по приведённым ссылкам.
|
||||
@@ -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. 🤓
|
||||
@@ -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>. 😎
|
||||
@@ -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>):
|
||||
|
||||

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

|
||||
|
||||
## Пример обновления { #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 будет автоматически обновлена, включая новое тело:
|
||||
|
||||

|
||||
|
||||
* Нажмите кнопку «Try it out», это позволит вам заполнить параметры и напрямую взаимодействовать с API:
|
||||
|
||||

|
||||
|
||||
* Затем нажмите кнопку «Execute», интерфейс свяжется с вашим API, отправит параметры, получит результаты и отобразит их на экране:
|
||||
|
||||

|
||||
|
||||
### Обновление альтернативной документации 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>.
|
||||
|
||||
* Альтернативная документация также отразит новый параметр запроса и тело:
|
||||
|
||||

|
||||
|
||||
### Подведём итоги { #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 ...
|
||||
```
|
||||
|
||||
...и посмотрите, как ваш редактор кода будет автоматически дополнять атрибуты и знать их типы:
|
||||
|
||||

|
||||
|
||||
Более полный пример с дополнительными возможностями см. в <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.
|
||||
@@ -0,0 +1,5 @@
|
||||
# Обучение { #learn }
|
||||
|
||||
Здесь представлены вводные разделы и учебные пособия для изучения **FastAPI**.
|
||||
|
||||
Вы можете считать это **книгой**, **курсом**, **официальным** и рекомендуемым способом изучения FastAPI. 😎
|
||||
@@ -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.
|
||||
@@ -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>.
|
||||
|
||||
///
|
||||
@@ -0,0 +1,3 @@
|
||||
# Ресурсы { #resources }
|
||||
|
||||
Дополнительные ресурсы, внешние ссылки, статьи и многое другое. ✈️
|
||||
@@ -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` были также подключены.
|
||||
@@ -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** таким образом, чтобы включить тело запроса в ключ, даже если объявлен только один параметр.
|
||||
@@ -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, сохраняя при этом простоту, краткость и элегантность вашего кода.
|
||||
|
||||
И дополнительно вы получаете:
|
||||
|
||||
* Поддержку редактора кода (автозавершение доступно везде!)
|
||||
* Преобразование данных (также известно как парсинг / сериализация)
|
||||
* Валидацию данных
|
||||
* Документацию схемы данных
|
||||
* Автоматическую генерацию документации
|
||||
@@ -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}.
|
||||
|
||||
///
|
||||
@@ -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**. 😎
|
||||
@@ -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`.
|
||||
@@ -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.
|
||||
|
||||
///
|
||||
@@ -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` для целой группы *операций пути*.
|
||||
@@ -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 | Подсказка
|
||||
|
||||
Все это может показаться не столь полезным на этих простых примерах.
|
||||
|
||||
Но вы увидите как это пригодится в главах посвященных безопасности.
|
||||
|
||||
И вы также увидите, сколько кода это вам сэкономит.
|
||||
|
||||
///
|
||||
@@ -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** внутри системы для преобразования данных. Однако он полезен и во многих других сценариях.
|
||||
|
||||
///
|
||||
@@ -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] *}
|
||||
@@ -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` и без пароля.
|
||||
@@ -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">'/home/user/code/awesomeapp'</font><b>]</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font> <b>(</b>Press CTRL+C
|
||||
to quit<b>)</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started reloader process <b>[</b><font color="#34E2E2"><b>383138</b></font><b>]</b> using WatchFiles
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>383153</b></font><b>]</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
В выводе будет строка примерно такого вида:
|
||||
|
||||
```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>):
|
||||
|
||||

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

|
||||
|
||||
### 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`.
|
||||
@@ -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**. 😎
|
||||
@@ -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** позаботится об их преобразовании.
|
||||
@@ -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">'/home/user/code/awesomeapp'</font><b>]</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font> <b>(</b>Press CTRL+C
|
||||
to quit<b>)</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started reloader process <b>[</b><font color="#34E2E2"><b>383138</b></font><b>]</b> using WatchFiles
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>383153</b></font><b>]</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
**НАСТОЯТЕЛЬНО рекомендуется** написать или скопировать код, отредактировать его и запустить локально.
|
||||
|
||||
Использование кода в вашем редакторе кода — это то, что действительно показывает преимущества FastAPI: вы увидите, как мало кода нужно написать, все проверки типов, автозавершение и т.д.
|
||||
|
||||
---
|
||||
|
||||
## Установка FastAPI { #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 }
|
||||
|
||||
Существует также **Продвинутое руководство пользователя**, которое вы сможете прочитать после **Учебник - Руководство пользователя**.
|
||||
|
||||
**Продвинутое руководство пользователя** основано на этом, использует те же концепции и обучает некоторым дополнительным функциям.
|
||||
|
||||
Но сначала вам следует прочитать **Учебник - Руководство пользователя** (то, что вы читаете прямо сейчас).
|
||||
|
||||
Оно спроектировано так, что вы можете создать полноценное приложение, используя только **Учебник - Руководство пользователя**, а затем расширить его различными способами, в зависимости от ваших потребностей, используя дополнительные идеи из **Продвинутого руководства пользователя**.
|
||||
@@ -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] *}
|
||||
@@ -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`.
|
||||
|
||||
Использование функций (вместо использования классов напрямую) нужно для того, чтобы ваш редактор не подсвечивал ошибки, связанные с их типами.
|
||||
|
||||
Таким образом вы можете использовать привычный вам редактор и инструменты разработки, не добавляя дополнительных конфигураций для игнорирования подобных ошибок.
|
||||
|
||||
///
|
||||
@@ -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`.
|
||||
|
||||
Смотрите следующие главы, чтобы узнать, как объявлять проверки для других типов, например чисел.
|
||||
@@ -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}.
|
||||
|
||||
///
|
||||
@@ -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
Reference in New Issue
Block a user