Browse Source
* Copy En pages * Translate pages (reviewed and corrected) * Apply changes from latest PRs: 13917 and 14099pull/14136/head
committed by
GitHub
37 changed files with 4713 additions and 0 deletions
@ -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,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,153 @@ |
|||
# Продвинутые зависимости { #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.118.0 и вы столкнулись с проблемами зависимостей с `yield`. |
|||
|
|||
/// |
|||
|
|||
Зависимости с `yield` со временем изменялись, чтобы учитывать разные случаи применения и исправлять проблемы. Ниже — краткое резюме изменений. |
|||
|
|||
### Зависимости с `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,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.io/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,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.io/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,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.io/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="[email protected]" 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` будет установлен в `"[email protected]"`. |
|||
|
|||
`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="[email protected]" |
|||
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.io/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.io/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.io/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.io/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.io/requests/" class="external-link" target="_blank">объекте `Request` на официальном сайте документации Starlette</a>. |
|||
|
|||
/// note | Технические детали |
|||
|
|||
Вы также можете использовать `from starlette.requests import Request`. |
|||
|
|||
**FastAPI** предоставляет его напрямую для удобства разработчика, но сам объект приходит из Starlette. |
|||
|
|||
/// |
@ -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,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,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,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.io/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,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,3 @@ |
|||
# Ресурсы { #resources } |
|||
|
|||
Дополнительные ресурсы, внешние ссылки, статьи и многое другое. ✈️ |
Loading…
Reference in new issue