36 changed files with 4071 additions and 0 deletions
@ -0,0 +1,247 @@ |
|||||
|
# Дополнительные ответы в OpenAPI |
||||
|
|
||||
|
/// warning | Предупреждение |
||||
|
|
||||
|
Это довольно сложная тема. |
||||
|
|
||||
|
Если вы только начинаете с **FastAPI**, вам это может не понадобиться. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
Вы можете объявить дополнительные ответы с дополнительными статус-кодами, медиа-типами, описаниями и т.д. |
||||
|
|
||||
|
Эти дополнительные ответы будут включены в OpenAPI схему, и они также будут отображаться в документации API. |
||||
|
|
||||
|
Но для этих дополнительных ответов вы должны убедиться, что возвращаете `Response`, например `JSONResponse`, напрямую с вашим статус-кодом и содержимым. |
||||
|
|
||||
|
## Дополнительный ответ с `model` |
||||
|
|
||||
|
Вы можете передать вашим *декораторам операций пути* параметр `responses`. |
||||
|
|
||||
|
Он принимает `dict`: ключи - это статус-коды для каждого ответа (такие как `200`), а значения - другие `dict` с информацией для каждого из них. |
||||
|
|
||||
|
Каждый из этих `dict` ответов может иметь ключ `model`, содержащий Pydantic модель, так же как и `response_model`. |
||||
|
|
||||
|
**FastAPI** возьмет эту модель, сгенерирует ее JSON Schema и включит в нужное место в OpenAPI. |
||||
|
|
||||
|
Например, чтобы объявить другой ответ с кодом состояния `404` и Pydantic моделью `Message`, можно написать: |
||||
|
|
||||
|
{* ../../docs_src/additional_responses/tutorial001.py hl[18,22] *} |
||||
|
|
||||
|
/// note | Примечание |
||||
|
|
||||
|
Имейте в виду, что вы должны вернуть `JSONResponse` напрямую. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
/// info | Информация |
||||
|
|
||||
|
Ключ `model` не является частью OpenAPI. |
||||
|
|
||||
|
**FastAPI** возьмет Pydantic модель оттуда, сгенерирует JSON Schema и разместит ее в нужном месте. |
||||
|
|
||||
|
Правильное место: |
||||
|
|
||||
|
* В ключе `content`, который имеет значение другого JSON объекта (`dict`), содержащего: |
||||
|
* Ключ с медиа-типом, например, `application/json`, который содержит как значение другой JSON объект, который содержит: |
||||
|
* Ключ `schema`, который имеет как значение JSON Schema из модели, вот это правильное место. |
||||
|
* **FastAPI** добавляет ссылку здесь на глобальные JSON Schema в другом месте вашего OpenAPI вместо того, чтобы включать её напрямую. Таким образом, другие приложения и клиенты могут использовать эти JSON Schema напрямую, предоставлять лучшие инструменты для генерации кода и т.д. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
Сгенерированные ответы в 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" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## Дополнительные медиа-типы для основного ответа |
||||
|
|
||||
|
Вы можете использовать тот же параметр `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` для любого дополнительного ответа, который связан с моделью. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Комбинирование информации |
||||
|
|
||||
|
Вы также можете объединять информацию об ответах из нескольких мест, включая параметры `response_model`, `status_code` и `responses`. |
||||
|
|
||||
|
Вы можете объявить `response_model`, используя статус-код по умолчанию `200` (или пользовательский, если вам нужно), а затем объявить дополнительную информацию для этого же ответа в `responses`, напрямую в OpenAPI схеме. |
||||
|
|
||||
|
**FastAPI** сохранит дополнительную информацию из `responses` и объединит её с JSON Schema вашей модели. |
||||
|
|
||||
|
Например, вы можете объявить ответ с кодом состояния `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"> |
||||
|
|
||||
|
## Комбинирование предопределенных ответов и пользовательских |
||||
|
|
||||
|
Возможно, вы захотите иметь некоторые предопределенные ответы, которые применяются к многим *операциям пути*, но вы хотите объединить их с пользовательскими ответами, необходимыми для каждой *операции пути*. |
||||
|
|
||||
|
В таких случаях вы можете использовать технику 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 ответах |
||||
|
|
||||
|
Чтобы узнать, что именно можно включить в ответы, вы можете проверить эти разделы в спецификации OpenAPI: |
||||
|
|
||||
|
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#responses-object" class="external-link" target="_blank">OpenAPI Responses Object</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">OpenAPI Response Object</a>, вы можете включить что угодно из этого напрямую для каждого ответа в вашем параметре `responses`. Включая `description`, `headers`, `content` (внутри этого вы объявляете различные медиа-типы и JSON schemas), и `links`. |
@ -0,0 +1,65 @@ |
|||||
|
# Продвинутые зависимости |
||||
|
|
||||
|
## Параметризованные зависимости |
||||
|
|
||||
|
Все зависимости, которые мы видели ранее, это фиксированные функции или классы. |
||||
|
|
||||
|
Но могут возникать случаи, когда вам нужно задать параметры зависимости, не объявляя множество разных функций или классов. |
||||
|
|
||||
|
Представим, что мы хотим иметь зависимость, которая проверяет, содержит ли параметр запроса `q` определенное фиксированное содержимое. |
||||
|
|
||||
|
Но мы хотим иметь возможность параметризовать это фиксированное содержимое. |
||||
|
|
||||
|
## Экземпляр "callable" |
||||
|
|
||||
|
В Python есть способ сделать экземпляр класса "вызваемым" (callable). |
||||
|
|
||||
|
Не сам класс (который уже является вызваемым), а именно экземпляр этого класса. |
||||
|
|
||||
|
Для этого мы объявляем метод `__call__`: |
||||
|
|
||||
|
{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[12] *} |
||||
|
|
||||
|
В этом случае этот `__call__` используется **FastAPI** для проверки дополнительных параметров и подзависимости, и это то, что будет вызвано для передачи значения в параметр в вашей функции *операции пути* позже. |
||||
|
|
||||
|
## Параметризация экземпляра |
||||
|
|
||||
|
Теперь мы можем использовать `__init__` для объявления параметров экземпляра, которые мы можем использовать для "параметризации" зависимости: |
||||
|
|
||||
|
{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[9] *} |
||||
|
|
||||
|
В этом случае **FastAPI** никогда не дотронется до или не будет интересоваться `__init__`, мы будем использовать его напрямую в нашем коде. |
||||
|
|
||||
|
## Создание экземпляра |
||||
|
|
||||
|
Мы можем создать экземпляр этого класса с помощью: |
||||
|
|
||||
|
{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[18] *} |
||||
|
|
||||
|
Таким образом, мы можем "параметризовать" нашу зависимость, которая теперь содержит `"bar"`, как атрибут `checker.fixed_content`. |
||||
|
|
||||
|
## Использование экземпляра в качестве зависимости |
||||
|
|
||||
|
Затем мы можем использовать этот `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 | Совет |
||||
|
|
||||
|
Все это может показаться надуманным. И может быть не совсем ясно, насколько это полезно. |
||||
|
|
||||
|
Эти примеры намеренно просты, но показывают, как все работает. |
||||
|
|
||||
|
В разделах о безопасности есть вспомогательные функции, которые реализованы точно таким же образом. |
||||
|
|
||||
|
Если вы поняли все это, вы уже знаете, как эти инструменты безопасности работают под капотом. |
||||
|
|
||||
|
/// |
@ -0,0 +1,361 @@ |
|||||
|
# За прокси |
||||
|
|
||||
|
В некоторых случаях вам может понадобиться воспользоваться сервером **прокси**, таким как Traefik или Nginx, с конфигурацией, которая добавляет дополнительный префикс пути, не видимый вашему приложению. |
||||
|
|
||||
|
В таких случаях вы можете использовать `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] *} |
||||
|
|
||||
|
И прокси будет **"обрезать"** **префикс пути** на лету перед передачей запроса на сервер приложения (вероятно, через FastAPI CLI через Uvicorn), оставляя ваше приложение уверенным в том, что оно обслуживается по адресу `/app`, чтобы вам не нужно было обновлять весь ваш код для включения префикса `/api/v1`. |
||||
|
|
||||
|
До этого момента все будет работать как обычно. |
||||
|
|
||||
|
Но затем, когда вы откроете интегрированный интерфейс документации (фронтенд), он будет ожидать, что получит схему OpenAPI по адресу `/openapi.json`, вместо `/api/v1/openapi.json`. |
||||
|
|
||||
|
Таким образом, фронтенд (который выполняется в браузере) попытается получить доступ к `/openapi.json` и не сможет получить схему OpenAPI. |
||||
|
|
||||
|
Поскольку у нас есть прокси с префиксом пути `/api/v1` для нашего приложения, фронтенд должен получить схему OpenAPI по адресу `/api/v1/openapi.json`. |
||||
|
|
||||
|
```mermaid |
||||
|
graph LR |
||||
|
|
||||
|
browser("Браузер") |
||||
|
proxy["Прокси на http://0.0.0.0:9999/api/v1/app"] |
||||
|
server["Сервер на http://127.0.0.1:8000/app"] |
||||
|
|
||||
|
browser --> proxy |
||||
|
proxy --> server |
||||
|
``` |
||||
|
|
||||
|
/// tip | Совет |
||||
|
|
||||
|
IP `0.0.0.0` обычно используется, чтобы указать, что программа слушает все доступные IP-адреса на этой машине/сервере. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
UI документации также будет нужна схема OpenAPI, чтобы указать, что этот API `server` находится по адресу `/api/v1` (за прокси). Например: |
||||
|
|
||||
|
```JSON hl_lines="4-8" |
||||
|
{ |
||||
|
"openapi": "3.1.0", |
||||
|
// Другие данные здесь |
||||
|
"servers": [ |
||||
|
{ |
||||
|
"url": "/api/v1" |
||||
|
} |
||||
|
], |
||||
|
"paths": { |
||||
|
// Другие данные здесь |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
В этом примере "Прокси" может быть чем-то вроде **Traefik**. А сервер будет чем-то вроде FastAPI CLI с **Uvicorn**, выполняющим ваше приложение FastAPI. |
||||
|
|
||||
|
### Предоставление `root_path` |
||||
|
|
||||
|
Для этого вы можете использовать параметр командной строки `--root-path`, например: |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
$ fastapi run main.py --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` |
||||
|
|
||||
|
Вы можете получить текущий `root_path`, используемый вашим приложением для каждого запроса, он является частью словаря `scope` (который является частью спецификации ASGI). |
||||
|
|
||||
|
Здесь мы приводим его в сообщении только для демонстрационных целей. |
||||
|
|
||||
|
{* ../../docs_src/behind_a_proxy/tutorial001.py hl[8] *} |
||||
|
|
||||
|
Затем, если вы запустите Uvicorn с: |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
$ fastapi run main.py --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": "Здравствуй, мир", |
||||
|
"root_path": "/api/v1" |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### Установка `root_path` в приложении FastAPI |
||||
|
|
||||
|
В качестве альтернативы, если у вас нет возможности предоставить параметр командной строки, такой как `--root-path` или эквивалентный, вы можете установить параметр `root_path` при создании вашего приложения FastAPI: |
||||
|
|
||||
|
{* ../../docs_src/behind_a_proxy/tutorial002.py hl[3] *} |
||||
|
|
||||
|
Передача `root_path` в `FastAPI` будет эквивалентна передаче параметра командной строки `--root-path` в Uvicorn или Hypercorn. |
||||
|
|
||||
|
### О `root_path` |
||||
|
|
||||
|
Учтите, что сервер (Uvicorn) не будет использовать этот `root_path` для чего-либо, кроме передачи его в приложение. |
||||
|
|
||||
|
Но если вы пойдете с вашим браузером на <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000/app</a>, вы увидите нормальный ответ: |
||||
|
|
||||
|
```JSON |
||||
|
{ |
||||
|
"message": "Здравствуй, мир", |
||||
|
"root_path": "/api/v1" |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
Таким образом, он не будет ожидать доступа по адресу `http://127.0.0.1:8000/api/v1/app`. |
||||
|
|
||||
|
Uvicorn будет ожидать, что прокси будет обращаться к Uvicorn по адресу `http://127.0.0.1:8000/app`, и тогда это будет обязанностью прокси добавить дополнительный префикс `/api/v1` поверх. |
||||
|
|
||||
|
## О прокси с обрезанным префиксом пути |
||||
|
|
||||
|
Учтите, что использование прокси с обрезанным префиксом пути — это только один из способов его настройки. |
||||
|
|
||||
|
Вероятно, в большинстве случаев по умолчанию прокси не будет иметь обрезанного префикса пути. |
||||
|
|
||||
|
В таком случае (без обрезанного префикса пути) прокси будет слушать что-то вроде `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 |
||||
|
|
||||
|
Вы можете легко запустить эксперимент локально с обрезанным префиксом пути, используя <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 --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> |
||||
|
|
||||
|
### Проверка ответов |
||||
|
|
||||
|
Теперь, если вы перейдете по 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": "Здравствуй, мир", |
||||
|
"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": "Здравствуй, мир", |
||||
|
"root_path": "/api/v1" |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
только на этот раз по адресу URL с префиксом, предоставленным прокси: `/api/v1`. |
||||
|
|
||||
|
Конечно, идея в том, чтобы все получали доступ к приложению через прокси, поэтому версия с префиксом пути `/api/v1` является "правильной". |
||||
|
|
||||
|
А версия без префикса пути (`http://127.0.0.1:8000/app`), предоставленная напрямую Uvicorn, будет исключительно для доступа _прокси_ (Traefik) к нему. |
||||
|
|
||||
|
Это демонстрирует, как прокси (Traefik) использует префикс пути и как сервер (Uvicorn) использует `root_path` из параметра `--root-path`. |
||||
|
|
||||
|
### Проверка UI документации |
||||
|
|
||||
|
Но вот интересная часть. ✨ |
||||
|
|
||||
|
"Официальный" способ доступа к приложению будет через прокси с префиксом пути, который мы определили. Таким образом, как мы и ожидали, если вы попытаетесь использовать 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"> |
||||
|
|
||||
|
Но если мы получим доступ к UI документации по "официальному" 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` для создания стандартного `server` в OpenAPI с URL, предоставленным `root_path`. |
||||
|
|
||||
|
## Дополнительные серверы |
||||
|
|
||||
|
/// warning | Предупреждение |
||||
|
|
||||
|
Это более сложный случай использования. Смело пропускайте его. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
По умолчанию **FastAPI** создаст `server` в схеме OpenAPI с URL для `root_path`. |
||||
|
|
||||
|
Но вы также можете предоставить другие альтернативные `servers`, например, если вы хотите, чтобы *тот же* UI документации взаимодействовал как с окружением для тестирования, так и с производственным окружением. |
||||
|
|
||||
|
Если вы передадите кастомный список `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`. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
В UI документации по адресу <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 | Совет |
||||
|
|
||||
|
UI документации будет взаимодействовать с сервером, который вы выберете. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
### Отключить автоматический сервер из `root_path` |
||||
|
|
||||
|
Если вы не хотите, чтобы **FastAPI** включал автоматический сервер, используя `root_path`, вы можете использовать параметр `root_path_in_servers=False`: |
||||
|
|
||||
|
{* ../../docs_src/behind_a_proxy/tutorial004.py hl[9] *} |
||||
|
|
||||
|
и тогда он не будет включен в схему OpenAPI. |
||||
|
|
||||
|
## Монтирование подприложения |
||||
|
|
||||
|
Если вам нужно смонтировать подприложение (как описано в [Подприложения - Монтирование](sub-applications.md){.internal-link target=_blank}), при этом используя прокси с `root_path`, вы можете сделать это нормально, как вы и ожидаете. |
||||
|
|
||||
|
FastAPI будет внутри использовать `root_path` умно, так что все будет просто работать. ✨ |
@ -0,0 +1,312 @@ |
|||||
|
# Пользовательский ответ - HTML, поток, файл и другие |
||||
|
|
||||
|
По умолчанию, **FastAPI** будет возвращать ответы, используя `JSONResponse`. |
||||
|
|
||||
|
Вы можете переопределить это, возвращая `Response` напрямую, как показано в [Возврат ответа напрямую](response-directly.md){.internal-link target=_blank}. |
||||
|
|
||||
|
Но если вы возвращаете `Response` напрямую (или любой подкласс, например, `JSONResponse`), данные не будут автоматически преобразованы (даже если вы объявите `response_model`), и документация не будет автоматически сгенерирована (например, включая конкретный "media type", в HTTP заголовке `Content-Type` как часть сгенерированного OpenAPI). |
||||
|
|
||||
|
Но вы также можете объявить `Response`, который вы хотите использовать (например, любой подкласс `Response`), в *декораторе операции пути* с использованием параметра `response_class`. |
||||
|
|
||||
|
Содержание, которое вы возвращаете из вашей *функции операции пути*, будет помещено внутрь этого `Response`. |
||||
|
|
||||
|
И если этот `Response` имеет тип носителя JSON (`application/json`), как в случае с `JSONResponse` и `UJSONResponse`, данные, которые вы возвращаете, будут автоматически преобразованы (и отфильтрованы) с любым Pydantic `response_model`, который вы объявили в *декораторе операции пути*. |
||||
|
|
||||
|
/// note | Заметка |
||||
|
|
||||
|
Если вы используете класс ответа без типа носителя, FastAPI ожидает, что ваш ответ будет без содержимого, поэтому формат ответа не будет задокументирован в сгенерированных OpenAPI документах. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Используйте `ORJSONResponse` |
||||
|
|
||||
|
Например, если вы хотите повысить производительность, вы можете установить и использовать <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a> и установить ответ в `ORJSONResponse`. |
||||
|
|
||||
|
Импортируйте класс `Response` (подкласс), который вы хотите использовать, и объявите его в *декораторе операции пути*. |
||||
|
|
||||
|
Для больших ответов возврат `Response` напрямую значительно быстрее, чем возврат словаря. |
||||
|
|
||||
|
Это потому, что по умолчанию, FastAPI будет проверять каждый элемент внутри и удостоверяться, что он сериализуем в JSON, используя тот же [Совместимый с JSON кодировщик](../tutorial/encoder.md){.internal-link target=_blank}, описанный в тутореале. Это то, что позволяет вам возвращать **произвольные объекты**, например, модели базы данных. |
||||
|
|
||||
|
Но если вы уверены, что содержание, которое вы возвращаете, **сериализуемо в JSON**, вы можете передать его напрямую в класс ответов и избежать дополнительной нагрузки, которая возникла бы у FastAPI, пропуская ваше возвращаемое содержимое через `jsonable_encoder` перед передачей его в класс ответов. |
||||
|
|
||||
|
{* ../../docs_src/custom_response/tutorial001b.py hl[2,7] *} |
||||
|
|
||||
|
/// info | Информация |
||||
|
|
||||
|
Параметр `response_class` также будет использоваться для определения "media type" ответа. |
||||
|
|
||||
|
В этом случае, HTTP заголовок `Content-Type` будет установлен в `application/json`. |
||||
|
|
||||
|
И он будет задокументирован как таковой в OpenAPI. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
/// tip | Совет |
||||
|
|
||||
|
`ORJSONResponse` доступен только в FastAPI, он недоступен в Starlette. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## HTML Ответ |
||||
|
|
||||
|
Чтобы вернуть ответ с HTML напрямую из **FastAPI**, используйте `HTMLResponse`. |
||||
|
|
||||
|
* Импортируйте `HTMLResponse`. |
||||
|
* Передайте `HTMLResponse` как параметр `response_class` вашего *декоратора операции пути*. |
||||
|
|
||||
|
{* ../../docs_src/custom_response/tutorial002.py hl[2,7] *} |
||||
|
|
||||
|
/// info | Информация |
||||
|
|
||||
|
Параметр `response_class` также будет использоваться для определения "media type" ответа. |
||||
|
|
||||
|
В этом случае, HTTP заголовок `Content-Type` будет установлен в `text/html`. |
||||
|
|
||||
|
И он будет задокументирован как таковой в OpenAPI. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
### Возврат `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` |
||||
|
|
||||
|
Если вы хотите переопределить ответ внутри функции, но в то же время задокументировать "media type" в OpenAPI, вы можете использовать параметр `response_class` И вернуть объект `Response`. |
||||
|
|
||||
|
`response_class` будет использоваться только для документирования *операции пути* в OpenAPI, но ваш `Response` будет использоваться как есть. |
||||
|
|
||||
|
#### Возврат `HTMLResponse` напрямую |
||||
|
|
||||
|
Например, это может выглядеть следующим образом: |
||||
|
|
||||
|
{* ../../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"> |
||||
|
|
||||
|
## Доступные ответы |
||||
|
|
||||
|
Вот некоторые из доступных ответов. |
||||
|
|
||||
|
Имейте в виду, что вы можете использовать `Response`, чтобы вернуть что-либо еще, или даже создать собственный подкласс. |
||||
|
|
||||
|
/// note | Технические подробности |
||||
|
|
||||
|
Вы также могли бы использовать `from starlette.responses import HTMLResponse`. |
||||
|
|
||||
|
**FastAPI** предоставляет тот же `starlette.responses` как `fastapi.responses` просто для вашего удобства как разработчика. Но большинство доступных ответов поступают непосредственно из Starlette. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
### `Response` |
||||
|
|
||||
|
Основной класс `Response`, все другие ответы наследуются от него. |
||||
|
|
||||
|
Вы можете вернуть его напрямую. |
||||
|
|
||||
|
Он принимает следующие параметры: |
||||
|
|
||||
|
* `content` - `str` или `bytes`. |
||||
|
* `status_code` - `int` HTTP код состояния. |
||||
|
* `headers` - `dict` строк. |
||||
|
* `media_type` - `str`, указывающая тип носителя. Например, `"text/html"`. |
||||
|
|
||||
|
FastAPI (фактически Starlette) автоматически включит заголовок Content-Length. Также будет включен заголовок Content-Type, основанный на `media_type` и добавлением кодировки для текстовых типов. |
||||
|
|
||||
|
{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *} |
||||
|
|
||||
|
### `HTMLResponse` |
||||
|
|
||||
|
Принимает некоторый текст или байты и возвращает HTML ответ, как вы прочли выше. |
||||
|
|
||||
|
### `PlainTextResponse` |
||||
|
|
||||
|
Принимает некоторый текст или байты и возвращает ответ с простым текстом. |
||||
|
|
||||
|
{* ../../docs_src/custom_response/tutorial005.py hl[2,7,9] *} |
||||
|
|
||||
|
### `JSONResponse` |
||||
|
|
||||
|
Принимает некоторые данные и возвращает закодированный ответ `application/json`. |
||||
|
|
||||
|
Это стандартный ответ, используемый в **FastAPI**, как вы прочли выше. |
||||
|
|
||||
|
### `ORJSONResponse` |
||||
|
|
||||
|
Быстрая альтернатива JSON ответу, используя <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a>, как вы прочли выше. |
||||
|
|
||||
|
/// info | Информация |
||||
|
|
||||
|
Это требует установки `orjson`, например с `pip install orjson`. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
### `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` |
||||
|
|
||||
|
Возвращает HTTP переадресацию. Использует код состояния 307 (Временная переадресация) по умолчанию. |
||||
|
|
||||
|
Вы можете вернуть `RedirectResponse` напрямую: |
||||
|
|
||||
|
{* ../../docs_src/custom_response/tutorial006.py hl[2,9] *} |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
Или вы можете использовать его в параметре `response_class`: |
||||
|
|
||||
|
{* ../../docs_src/custom_response/tutorial006b.py hl[2,7,9] *} |
||||
|
|
||||
|
Если вы сделаете это, тогда вы можете вернуть URL напрямую из вашей *функции операции пути*. |
||||
|
|
||||
|
В этом случае, используемый `status_code` будет таким же, как стандартный для `RedirectResponse`, который равен `307`. |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
Вы также можете использовать параметр `status_code`, комбинируя его с параметром `response_class`: |
||||
|
|
||||
|
{* ../../docs_src/custom_response/tutorial006c.py hl[2,7,9] *} |
||||
|
|
||||
|
### `StreamingResponse` |
||||
|
|
||||
|
Принимает асинхронный генератор или обычный генератор/итератор и потоково передает тело ответа. |
||||
|
|
||||
|
{* ../../docs_src/custom_response/tutorial007.py hl[2,14] *} |
||||
|
|
||||
|
#### Использование `StreamingResponse` с объектами подобными файлам |
||||
|
|
||||
|
Если у вас есть объект подобный файлу (например, объект, возвращаемый `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` |
||||
|
|
||||
|
Асинхронно потоком передает файл в качестве ответа. |
||||
|
|
||||
|
Принимает другой набор аргументов для инстанцирования по сравнению с другими типами ответов: |
||||
|
|
||||
|
* `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] *} |
||||
|
|
||||
|
В этом случае вы можете вернуть путь к файлу напрямую из вашей *функции операции пути*. |
||||
|
|
||||
|
## Пользовательский класс ответа |
||||
|
|
||||
|
Вы можете создать собственный пользовательский класс ответа, наследуясь от `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. 😉 |
||||
|
|
||||
|
## Класс ответа по умолчанию |
||||
|
|
||||
|
При создании экземпляра класса **FastAPI** или `APIRouter` вы можете указать, какой класс ответа использовать по умолчанию. |
||||
|
|
||||
|
Параметр, который определяет это — `default_response_class`. |
||||
|
|
||||
|
В примере ниже, **FastAPI** будет использовать `ORJSONResponse` по умолчанию, во всех *операциях пути*, вместо `JSONResponse`. |
||||
|
|
||||
|
{* ../../docs_src/custom_response/tutorial010.py hl[2,4] *} |
||||
|
|
||||
|
/// tip | Совет |
||||
|
|
||||
|
Вы все равно можете переопределить `response_class` в *операциях пути* как прежде. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Дополнительная документация |
||||
|
|
||||
|
Вы также можете объявить тип носителя и многие другие детали в OpenAPI, используя `responses`: [Дополнительные ответы в OpenAPI](additional-responses.md){.internal-link target=_blank}. |
@ -0,0 +1,95 @@ |
|||||
|
# Использование Dataclasses |
||||
|
|
||||
|
FastAPI построен на **Pydantic**, и я показывал вам, как использовать модели Pydantic для объявления запросов и ответов. |
||||
|
|
||||
|
Но 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` в параметре `response_model`: |
||||
|
|
||||
|
{* ../../docs_src/dataclasses/tutorial002.py hl[1,7:13,19] *} |
||||
|
|
||||
|
Dataclass будет автоматически преобразован в dataclass Pydantic. |
||||
|
|
||||
|
Таким образом, его схема будет отображаться в пользовательском интерфейсе API документации: |
||||
|
|
||||
|
<img src="/img/tutorial/dataclasses/image01.png"> |
||||
|
|
||||
|
## Dataclasses в вложенных структурах данных |
||||
|
|
||||
|
Вы также можете комбинировать `dataclasses` с другими аннотациями типов, чтобы создавать вложенные структуры данных. |
||||
|
|
||||
|
В некоторых случаях вам все же может понадобиться использовать версию `dataclasses` от Pydantic. Например, если у вас возникают ошибки с автоматически сгенерированной документацией API. |
||||
|
|
||||
|
В этом случае вы можете просто заменить стандартные `dataclasses` на `pydantic.dataclasses`, что является полноценной заменой: |
||||
|
|
||||
|
{* ../../docs_src/dataclasses/tutorial003.py hl[1,5,8:11,14:17,23:25,28] *} |
||||
|
|
||||
|
1. Мы по-прежнему импортируем `field` из стандартных `dataclasses`. |
||||
|
|
||||
|
2. `pydantic.dataclasses` является полноценной заменой для `dataclasses`. |
||||
|
|
||||
|
3. Dataclass `Author` включает в себя список `Item` dataclasses. |
||||
|
|
||||
|
4. Dataclass `Author` используется как параметр `response_model`. |
||||
|
|
||||
|
5. Вы можете использовать другие стандартные аннотации типов с dataclasses в качестве тела запроса. |
||||
|
|
||||
|
В этом случае это список `Item` dataclasses. |
||||
|
|
||||
|
6. Здесь мы возвращаем словарь, содержащий `items`, который представляет собой список dataclasses. |
||||
|
|
||||
|
FastAPI все еще способен <abbr title="преобразовать данные в формат, который может быть передан">сериализовать</abbr> данные в JSON. |
||||
|
|
||||
|
7. Здесь `response_model` использует аннотацию типа списка `Author` dataclasses. |
||||
|
|
||||
|
Опять же, вы можете сочетать `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) для преобразования ответа. |
||||
|
|
||||
|
Вы можете комбинировать `dataclasses` с другими аннотациями типов во множестве различных комбинаций для формирования сложных структур данных. |
||||
|
|
||||
|
Проверьте советы по аннотациям в коде выше, чтобы увидеть более конкретные детали. |
||||
|
|
||||
|
## Узнать больше |
||||
|
|
||||
|
Вы также можете комбинировать `dataclasses` с другими моделями Pydantic, наследовать от них, включать их в свои собственные модели и т. д. |
||||
|
|
||||
|
Чтобы узнать больше, ознакомьтесь с <a href="https://docs.pydantic.dev/latest/concepts/dataclasses/" class="external-link" target="_blank">документацией Pydantic о dataclasses</a>. |
||||
|
|
||||
|
## Версия |
||||
|
|
||||
|
Это доступно с версии FastAPI `0.67.0`. 🔖 |
@ -0,0 +1,165 @@ |
|||||
|
# События жизненного цикла |
||||
|
|
||||
|
Вы можете определить логику (код), которая должна выполняться перед тем, как приложение **запустится**. Это означает, что этот код будет выполнен **один раз**, **до** того как приложение **начнет принимать запросы**. |
||||
|
|
||||
|
Таким же образом, вы можете определить логику (код), которая должна выполняться при **завершении работы** приложения. В этом случае этот код будет выполнен **один раз**, **после** обработки возможных **многочисленных запросов**. |
||||
|
|
||||
|
Так как этот код выполняется до того, как приложение **начнет** принимать запросы, и сразу после того как оно **закончит** их обрабатывать, он покрывает весь **жизненный цикл** приложения (слово "жизненный цикл" будет важно через секунду 😉). |
||||
|
|
||||
|
Это может быть очень полезно для настройки **ресурсов**, которые вам нужно использовать для всего приложения, и которые **разделяются** между запросами, и/или которые нужно **очистить** впоследствии. Например, пул соединений с базой данных или загрузка общего алгоритма машинного обучения. |
||||
|
|
||||
|
## Пример использования |
||||
|
|
||||
|
Начнем с примера **использования**, а затем посмотрим, как это решить. |
||||
|
|
||||
|
Представьте, что у вас есть несколько **моделей машинного обучения**, которые вы хотите использовать для обработки запросов. 🤖 |
||||
|
|
||||
|
Эти же модели разделяются между запросами, так что это не одна модель на запрос или одна на пользователя или что-то подобное. |
||||
|
|
||||
|
Представьте, что загрузка модели может **занимать довольно много времени**, потому что ей нужно прочесть много **данных с диска**. Поэтому вы не хотите делать это для каждого запроса. |
||||
|
|
||||
|
Вы могли бы загрузить её на верхнем уровне модуля/файла, но это также означало бы, что модель будет **загружена**, даже если вы просто запускаете простой автоматический тест, и тогда этот тест будет **медленным**, потому что ему придется ждать загрузки модели перед выполнением независимой части кода. |
||||
|
|
||||
|
Вот это мы и решим: загрузим модель перед обработкой запросов, но только прямо перед тем, как приложение начнёт принимать запросы, а не во время загрузки кода. |
||||
|
|
||||
|
## Жизненный цикл |
||||
|
|
||||
|
Вы можете определить логику для *запуска* и *завершения работы* с использованием параметра `lifespan` приложения `FastAPI` и "контекстного менеджера" (я покажу вам, что это такое, через секунду). |
||||
|
|
||||
|
Начнем с примера, а затем рассмотрим его подробно. |
||||
|
|
||||
|
Мы создаем асинхронную функцию `lifespan()` с использованием `yield`, как показано ниже: |
||||
|
|
||||
|
{* ../../docs_src/events/tutorial003.py hl[16,19] *} |
||||
|
|
||||
|
Здесь мы симулируем дорогую *операцию старта* загрузки модели, помещая (фейковую) функцию модели в словарь с моделями машинного обучения до `yield`. Этот код будет выполнен **до** того, как приложение **начнет принимать запросы**, в момент *старта*. |
||||
|
|
||||
|
А затем, сразу после `yield`, мы выгружаем модель. Этот код будет выполнен **после** того, как приложение **закончит обрабатывать запросы**, прямо перед *завершением работы*. Это может, например, освободить такие ресурсы, как память или GPU. |
||||
|
|
||||
|
/// tip | Совет |
||||
|
|
||||
|
Завершение работы происходит, когда вы **останавливаете** приложение. |
||||
|
|
||||
|
Возможно, вам нужно запустить новую версию или вы просто устали его запускать. 🤷 |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
### Функция жизненного цикла |
||||
|
|
||||
|
Первое, на что стоит обратить внимание, это то, что мы определяем асинхронную функцию с `yield`. Это очень похоже на зависимости с `yield`. |
||||
|
|
||||
|
{* ../../docs_src/events/tutorial003.py hl[14:19] *} |
||||
|
|
||||
|
Первая часть функции, до `yield`, будет выполнена **до** того, как приложение стартует. |
||||
|
|
||||
|
А часть после `yield` будет выполнена **после** того, как приложение завершит работу. |
||||
|
|
||||
|
### Асинхронный контекстный менеджер |
||||
|
|
||||
|
Если вы посмотрите, то заметите, что функция декорирована с помощью `@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] *} |
||||
|
|
||||
|
## Альтернативные события (устаревшие) |
||||
|
|
||||
|
/// warning | Предупреждение |
||||
|
|
||||
|
Рекомендуемый способ управления *запуском* и *завершением работы* — использовать параметр `lifespan` приложения `FastAPI`, как описано выше. Если вы предоставите параметр `lifespan`, обработчики событий `startup` и `shutdown` больше не будут вызываться. Используются либо `lifespan`, либо событийные обработчики, но не оба. |
||||
|
|
||||
|
Вы, вероятно, можете пропустить эту часть. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
Существует альтернативный способ определения этой логики для выполнения в момент *запуска* и *завершения работы*. |
||||
|
|
||||
|
Вы можете определить обработчики событий (функции), которые должны быть выполнены до запуска приложения, или при завершении работы приложения. |
||||
|
|
||||
|
Эти функции могут быть объявлены с `async def` или обычными `def`. |
||||
|
|
||||
|
### Событие `startup` |
||||
|
|
||||
|
Чтобы добавить функцию, которая должна выполняться перед началом работы приложения, объявите её с событием `"startup"`: |
||||
|
|
||||
|
{* ../../docs_src/events/tutorial001.py hl[8] *} |
||||
|
|
||||
|
В этом случае функция-обработчик события `startup` инициализирует "базу данных" элементов (это просто `dict`) с некоторыми значениями. |
||||
|
|
||||
|
Можно добавить более одной функции-обработчика события. |
||||
|
|
||||
|
И ваше приложение не начнет принимать запросы, пока не завершатся все обработчики события `startup`. |
||||
|
|
||||
|
### Событие `shutdown` |
||||
|
|
||||
|
Чтобы добавить функцию, которая должна выполняться при завершении работы приложения, объявите её с событием `"shutdown"`: |
||||
|
|
||||
|
{* ../../docs_src/events/tutorial002.py hl[6] *} |
||||
|
|
||||
|
Здесь функция-обработчик события `shutdown` запишет текстовую строку `"Application shutdown"` в файл `log.txt`. |
||||
|
|
||||
|
/// info | Информация |
||||
|
|
||||
|
В функции `open()`, параметр `mode="a"` означает "добавление", то есть строка будет добавлена после того, что уже есть в файле, без перезаписи предыдущего содержимого. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
/// tip | Совет |
||||
|
|
||||
|
Обратите внимание, что в этом случае мы используем стандартную функцию Python `open()`, которая взаимодействует с файлом. |
||||
|
|
||||
|
Таким образом, это включает ввод/вывод (I/O), который требует "ожидания", чтобы данные были записаны на диск. |
||||
|
|
||||
|
Но `open()` не использует `async` и `await`. |
||||
|
|
||||
|
Поэтому мы объявляем функцию-обработчик событий с использованием стандартного `def` вместо `async def`. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
### `startup` и `shutdown` вместе |
||||
|
|
||||
|
Существует высокая вероятность того, что логика вашего *запуска* и *завершения работы* связана; возможно, вы хотите что-то начать, а затем завершить, получить ресурс, а потом его освободить и т.д. |
||||
|
|
||||
|
Выполнение этого в отдельных функциях, которые не разделяют логику или переменные, сложнее, поскольку вам нужно будет сохранять значения в глобальных переменных или использовать подобные трюки. |
||||
|
|
||||
|
По этой причине теперь рекомендуется вместо этого использовать `lifespan`, как объяснено выше. |
||||
|
|
||||
|
## Технические детали |
||||
|
|
||||
|
Просто техническая деталь для любопытных. 🤓 |
||||
|
|
||||
|
В ASGI технической спецификации это часть <a href="https://asgi.readthedocs.io/en/latest/specs/lifespan.html" class="external-link" target="_blank">оглашения Жизненного Цикла</a>, и она определяет события `startup` и `shutdown`. |
||||
|
|
||||
|
/// info | Информация |
||||
|
|
||||
|
Вы можете прочитать больше о обработчиках `lifespan` в Starlette в <a href="https://www.starlette.io/lifespan/" class="external-link" target="_blank">документации Starlette's Lifespan</a>. |
||||
|
|
||||
|
В том числе то, как справляться с состоянием жизненного цикла, которое может быть использовано в других областях вашего кода. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Подпрограммы |
||||
|
|
||||
|
🚨 Помните, что эти события жизненного цикла (startup и shutdown) будут выполнены только для основного приложения, а не для [Подпрограмм - Монтирования](sub-applications.md){.internal-link target=_blank}. |
@ -0,0 +1,261 @@ |
|||||
|
# Генерация клиентов |
||||
|
|
||||
|
Так как **FastAPI** основан на спецификации OpenAPI, вы получаете автоматическую совместимость со многими инструментами, включая автоматическую документацию API (предоставленную Swagger UI). |
||||
|
|
||||
|
Одним из преимуществ, не всегда очевидных, является возможность **генерировать клиентов** (иногда их называют <abbr title="Software Development Kits">**SDK**</abbr>) для вашего API на различных **языках программирования**. |
||||
|
|
||||
|
## Генераторы клиентов OpenAPI |
||||
|
|
||||
|
Существует множество инструментов для генерации клиентов из **OpenAPI**. |
||||
|
|
||||
|
Распространённый инструмент - это <a href="https://openapi-generator.tech/" class="external-link" target="_blank">OpenAPI Generator</a>. |
||||
|
|
||||
|
Если вы создаёте **фронтенд**, отличной альтернативой является <a href="https://github.com/hey-api/openapi-ts" class="external-link" target="_blank">openapi-ts</a>. |
||||
|
|
||||
|
## Генераторы клиентов и SDK - спонсор |
||||
|
|
||||
|
Существуют также некоторые **поддерживаемые компаниями** генераторы клиентов и SDK на базе OpenAPI (FastAPI), которые в некоторых случаях могут предложить вам **дополнительные функции** поверх высококачественно сгенерированных SDK/клиентов. |
||||
|
|
||||
|
Некоторые из них также ✨ [**спонсируют FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, что обеспечивает продолжение и здоровое **развитие** FastAPI и его **экосистемы**. |
||||
|
|
||||
|
Это показывает их истинную приверженность 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.stainlessapi.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> |
||||
|
|
||||
|
Существуют также другие компании, предлагающие аналогичные услуги, которые вы можете найти и изучить в сети. 🤓 |
||||
|
|
||||
|
## Генерация TypeScript клиента для фронтенда |
||||
|
|
||||
|
Начнём с простого приложения на FastAPI: |
||||
|
|
||||
|
{* ../../docs_src/generate_clients/tutorial001_py39.py hl[7:9,12:13,16:17,21] *} |
||||
|
|
||||
|
Обратите внимание, что *операции пути* определяют модели, которые они используют для запросов и ответов, используя модели `Item` и `ResponseMessage`. |
||||
|
|
||||
|
### Документация API |
||||
|
|
||||
|
Если вы перейдёте к документации API, вы увидите, что она содержит **схемы** для данных, которые должны быть отправлены в запросах и получены в ответах: |
||||
|
|
||||
|
<img src="/img/tutorial/generate-clients/image01.png"> |
||||
|
|
||||
|
Вы можете увидеть эти схемы, так как они были объявлены с использованием моделей в приложении. |
||||
|
|
||||
|
Эта информация доступна в **схеме OpenAPI** приложения и затем отображается в документации API (с помощью Swagger UI). |
||||
|
|
||||
|
И эта же информация из моделей, включённых в OpenAPI, может быть использована для **генерации кода клиента**. |
||||
|
|
||||
|
### Генерация клиента на TypeScript |
||||
|
|
||||
|
Теперь, когда у нас есть приложение с моделями, мы можем сгенерировать код клиента для фронтенда. |
||||
|
|
||||
|
#### Установить `openapi-ts` |
||||
|
|
||||
|
Вы можете установить `openapi-ts` в код вашего фронтенда с помощью: |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
$ npm install @hey-api/openapi-ts --save-dev |
||||
|
|
||||
|
---> 100% |
||||
|
``` |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
#### Генерация клиентского кода |
||||
|
|
||||
|
Для генерации клиентского кода вы можете использовать приложение командной строки `openapi-ts`, которое теперь будет установлено. |
||||
|
|
||||
|
Поскольку оно установлено в локальном проекте, вы, вероятно, не сможете вызывать эту команду напрямую, но сможете прописать её в файле `package.json`. |
||||
|
|
||||
|
Он может выглядеть следующим образом: |
||||
|
|
||||
|
```JSON hl_lines="7" |
||||
|
{ |
||||
|
"name": "frontend-app", |
||||
|
"version": "1.0.0", |
||||
|
"description": "", |
||||
|
"main": "index.js", |
||||
|
"scripts": { |
||||
|
"generate-client": "openapi-ts --input http://localhost:8000/openapi.json --output ./src/client --client axios" |
||||
|
}, |
||||
|
"author": "", |
||||
|
"license": "", |
||||
|
"devDependencies": { |
||||
|
"@hey-api/openapi-ts": "^0.27.38", |
||||
|
"typescript": "^4.6.2" |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
После добавления этого скрипта NPM `generate-client` вы можете запустить его с помощью: |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
$ npm run generate-client |
||||
|
|
||||
|
frontend-app@1.0.0 generate-client /home/user/code/frontend-app |
||||
|
> openapi-ts --input http://localhost:8000/openapi.json --output ./src/client --client axios |
||||
|
``` |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
Эта команда сгенерирует код в `./src/client` и будет использовать `axios` (библиотеку HTTP для фронтенда). |
||||
|
|
||||
|
### Попробуйте использовать клиентский код |
||||
|
|
||||
|
Теперь вы можете импортировать и использовать клиентский код, это может выглядеть так, обратите внимание, что вы получите автодополнение для методов: |
||||
|
|
||||
|
<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 будет более крупным, и, вероятно, вы будете использовать теги для разделения различных групп *операций пути*. |
||||
|
|
||||
|
Например, у вас может быть раздел для **элементов** и другой раздел для **пользователей**, и они могут быть отделены тегами: |
||||
|
|
||||
|
{* ../../docs_src/generate_clients/tutorial002_py39.py hl[21,26,34] *} |
||||
|
|
||||
|
### Генерация TypeScript клиента с тегами |
||||
|
|
||||
|
Если вы генерируете клиента для приложения FastAPI, использующего теги, это обычно также разделит клиентский код на основе тегов. |
||||
|
|
||||
|
Таким образом, вы сможете правильно упорядочить и сгруппировать код клиента: |
||||
|
|
||||
|
<img src="/img/tutorial/generate-clients/image06.png"> |
||||
|
|
||||
|
В этом случае у вас есть: |
||||
|
|
||||
|
* `ItemsService` |
||||
|
* `UsersService` |
||||
|
|
||||
|
### Имена клиентских методов |
||||
|
|
||||
|
В настоящий момент сгенерированные имена методов, такие как `createItemItemsPost`, выглядят не очень чисто: |
||||
|
|
||||
|
```TypeScript |
||||
|
ItemsService.createItemItemsPost({name: "Plumbus", price: 5}) |
||||
|
``` |
||||
|
|
||||
|
... это потому, что генератор клиента использует внутренний **идентификатор операции** OpenAPI для каждой *операции пути*. |
||||
|
|
||||
|
OpenAPI требует, чтобы каждый идентификатор операции был уникальным для всех *операций пути*, поэтому FastAPI использует **имя функции**, **путь** и **HTTP метод/операцию** для генерации этого идентификатора операции, так как это позволяет убедиться, что идентификаторы операций уникальны. |
||||
|
|
||||
|
Но я покажу вам, как это улучшить. 🤓 |
||||
|
|
||||
|
## Настраиваемые идентификаторы операций и улучшенные имена методов |
||||
|
|
||||
|
Вы можете **изменить** способ генерации этих идентификаторов операций, чтобы сделать их проще и получить **более простые имена методов** в клиентах. |
||||
|
|
||||
|
В этом случае вам придётся обеспечить уникальность каждого идентификатора операции другим способом. |
||||
|
|
||||
|
Например, вы можете удостовериться, что каждая *операция пути* имеет тег, а затем генерировать идентификатор операции на основе **тега** и **имени** операции пути (имени функции). |
||||
|
|
||||
|
### Настраиваемая функция генерации уникального идентификатора |
||||
|
|
||||
|
FastAPI использует **уникальный идентификатор** для каждой *операции пути*, он используется для **идентификатора операции**, а также для имен необходимых пользовательских моделей, для запросов или ответов. |
||||
|
|
||||
|
Вы можете настроить эту функцию. Она принимает `APIRoute` и возвращает строку. |
||||
|
|
||||
|
Например, здесь используется первый тег (вероятно, у вас будет только один тег) и имя операции пути (имя функции). |
||||
|
|
||||
|
Вы можете передать эту пользовательскую функцию в **FastAPI** как параметр `generate_unique_id_function`: |
||||
|
|
||||
|
{* ../../docs_src/generate_clients/tutorial003_py39.py hl[6:7,10] *} |
||||
|
|
||||
|
### Генерация TypeScript клиента с пользовательскими идентификаторами операций |
||||
|
|
||||
|
Теперь, если вы снова сгенерируете клиента, вы увидите, что у него улучшенные имена методов: |
||||
|
|
||||
|
<img src="/img/tutorial/generate-clients/image07.png"> |
||||
|
|
||||
|
Как видите, имена методов теперь включают тег и имя функции, они больше не содержат информацию из URL пути и HTTP операции. |
||||
|
|
||||
|
### Предварительная обработка спецификации OpenAPI для генерации клиента |
||||
|
|
||||
|
Сгенерированный код всё ещё содержит некоторую **дублированную информацию**. |
||||
|
|
||||
|
Мы уже знаем, что этот метод связан с **items** (элементами), так как это слово содержится в `ItemsService` (взятом из тега), но у нас всё ещё есть название тега в начале имени метода. 😕 |
||||
|
|
||||
|
Возможно, мы всё же захотим оставить его для OpenAPI в целом, так как это обеспечит уникальность идентификаторов операций. |
||||
|
|
||||
|
Но для сгенерированного клиента мы могли бы **изменить** идентификаторы операций OpenAPI прямо перед генерацией клиентов, чтобы сделать имена методов более красивыми и **чистыми**. |
||||
|
|
||||
|
Мы могли бы загрузить JSON OpenAPI в файл `openapi.json`, а затем могли бы **удалить этот префиксированный тег** при помощи скрипта, как здесь: |
||||
|
|
||||
|
{* ../../docs_src/generate_clients/tutorial004.py *} |
||||
|
|
||||
|
//// tab | Node.js |
||||
|
|
||||
|
```Javascript |
||||
|
{!> ../../docs_src/generate_clients/tutorial004.js!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
Благодаря этому идентификаторы операций будут переименованы из `items-get_items` в просто `get_items`, что позволит генератору клиента создать более простые имена методов. |
||||
|
|
||||
|
### Генерация TypeScript клиента с предварительно обработанным OpenAPI |
||||
|
|
||||
|
Теперь, так как конечный результат находится в файле `openapi.json`, вы можете изменить `package.json`, чтобы использовать этот локальный файл, например: |
||||
|
|
||||
|
```JSON hl_lines="7" |
||||
|
{ |
||||
|
"name": "frontend-app", |
||||
|
"version": "1.0.0", |
||||
|
"description": "", |
||||
|
"main": "index.js", |
||||
|
"scripts": { |
||||
|
"generate-client": "openapi-ts --input ./openapi.json --output ./src/client --client axios" |
||||
|
}, |
||||
|
"author": "", |
||||
|
"license": "", |
||||
|
"devDependencies": { |
||||
|
"@hey-api/openapi-ts": "^0.27.38", |
||||
|
"typescript": "^4.6.2" |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
После генерации нового клиента вы теперь получите **чистые имена методов**, с всеми **автодополнениями**, **встроенными ошибками** и прочим: |
||||
|
|
||||
|
<img src="/img/tutorial/generate-clients/image08.png"> |
||||
|
|
||||
|
## Преимущества |
||||
|
|
||||
|
При использовании автоматически сгенерированных клиентов вы получите **автодополнение** для: |
||||
|
|
||||
|
* Методов. |
||||
|
* Загружаемых данных в теле запроса, параметрах запроса и т. д. |
||||
|
* Полей в ответах. |
||||
|
|
||||
|
Вы также получите **встроенные ошибки** для всего. |
||||
|
|
||||
|
И всякий раз, когда вы обновляете код серверной части и **перегенерируете** фронтенд, он будет иметь все новые *операции пути* как методы, старые будут удалены, и любые другие изменения будут отражены в сгенерированном коде. 🤓 |
||||
|
|
||||
|
Это также значит, что если что-то изменилось, это будет **отражено** в клиентском коде автоматически. И если вы **соберёте** клиента, вы получите ошибку, если данные не совпадают. |
||||
|
|
||||
|
Таким образом, вы сможете **обнаружить многие ошибки** на ранней стадии цикла разработки, вместо того чтобы ждать, пока ошибки проявятся у ваших конечных пользователей в производстве, и затем пытаться выяснить, где находится проблема. ✨ |
@ -0,0 +1,96 @@ |
|||||
|
# Продвинутые Middleware |
||||
|
|
||||
|
В основном учебнике вы прочитали, как добавить [Custom Middleware](../tutorial/middleware.md){.internal-link target=_blank} в ваше приложение. |
||||
|
|
||||
|
Также вы узнали, как обрабатывать [CORS с помощью `CORSMiddleware`](../tutorial/cors.md){.internal-link target=_blank}. |
||||
|
|
||||
|
В этой секции мы рассмотрим, как использовать другие middlewares. |
||||
|
|
||||
|
## Добавление ASGI middlewares |
||||
|
|
||||
|
Так как **FastAPI** основан на Starlette и реализует спецификацию <abbr title="Asynchronous Server Gateway Interface">ASGI</abbr>, вы можете использовать любое ASGI middleware. |
||||
|
|
||||
|
Middleware не обязательно должен быть создан для FastAPI или Starlette, чтобы работать, до тех пор, пока он следует спецификации ASGI. |
||||
|
|
||||
|
В общем, ASGI middlewares - это классы, которые ожидают получить ASGI приложение в качестве первого аргумента. |
||||
|
|
||||
|
Таким образом, в документации для сторонних ASGI middlewares вероятно, вас попросят сделать что-то похожее на: |
||||
|
|
||||
|
```Python |
||||
|
from unicorn import UnicornMiddleware |
||||
|
|
||||
|
app = SomeASGIApp() |
||||
|
|
||||
|
new_app = UnicornMiddleware(app, some_config="rainbow") |
||||
|
``` |
||||
|
|
||||
|
Но FastAPI (на самом деле Starlette) предоставляет более простой способ сделать это, который гарантирует, что внутренние middleware будут правильно обрабатывать ошибки сервера и пользовательские обработчики исключений. |
||||
|
|
||||
|
Для этого используйте `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. |
||||
|
|
||||
|
## Встроенные middlewares |
||||
|
|
||||
|
**FastAPI** включает несколько middlewares для общих случаев использования, дальше мы рассмотрим как их использовать. |
||||
|
|
||||
|
/// note | Технические детали |
||||
|
|
||||
|
Для следующих примеров вы также могли бы использовать `from starlette.middleware.something import SomethingMiddleware`. |
||||
|
|
||||
|
**FastAPI** предоставляет несколько middlewares в `fastapi.middleware` просто для вашего удобства, как разработчика. Но большинство доступных middlewares приходят непосредственно из Starlette. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## `HTTPSRedirectMiddleware` |
||||
|
|
||||
|
Обеспечивает, чтобы все входящие запросы были либо `https`, либо `wss`. |
||||
|
|
||||
|
Любой входящий запрос на `http` или `ws` будет перенаправлен на защищённую схему. |
||||
|
|
||||
|
{* ../../docs_src/advanced_middleware/tutorial001.py hl[2,6] *} |
||||
|
|
||||
|
## `TrustedHostMiddleware` |
||||
|
|
||||
|
Обеспечивает, чтобы все входящие запросы имели правильно установленный заголовок `Host`, чтобы защититься от атак посредством подмены заголовка HTTP Host. |
||||
|
|
||||
|
{* ../../docs_src/advanced_middleware/tutorial002.py hl[2,6:8] *} |
||||
|
|
||||
|
Поддерживаются следующие аргументы: |
||||
|
|
||||
|
* `allowed_hosts` - Список доменных имён, которые должны быть разрешены в качестве имён хоста. Поддерживаются подстановочные домены, такие как `*.example.com`, для совпадения с поддоменами. Чтобы разрешить любое имя хоста, либо используйте `allowed_hosts=["*"]`, либо пропустите middleware. |
||||
|
|
||||
|
Если входящий запрос проходит проверку неправильно, то будет отправлен ответ `400`. |
||||
|
|
||||
|
## `GZipMiddleware` |
||||
|
|
||||
|
Обрабатывает GZip ответы для любого запроса, который включает `"gzip"` в заголовке `Accept-Encoding`. |
||||
|
|
||||
|
Middleware обрабатывает как стандартные, так и потоковые ответы. |
||||
|
|
||||
|
{* ../../docs_src/advanced_middleware/tutorial003.py hl[2,6] *} |
||||
|
|
||||
|
Поддерживаются следующие аргументы: |
||||
|
|
||||
|
* `minimum_size` - Не использовать GZip для ответов, размер которых меньше этого минимума в байтах. По умолчанию `500`. |
||||
|
* `compresslevel` - Используется во время GZip сжатия. Это целое число в диапазоне от 1 до 9. Значение по умолчанию `9`. Меньшее значение приводит к более быстрому сжатию, но большим размерам файлов, в то время как большее значение приводит к более медленному сжатию, но меньшим размерам файлов. |
||||
|
|
||||
|
## Другие middlewares |
||||
|
|
||||
|
Существует много других ASGI middlewares. |
||||
|
|
||||
|
Например: |
||||
|
|
||||
|
* <a href="https://github.com/encode/uvicorn/blob/master/uvicorn/middleware/proxy_headers.py" class="external-link" target="_blank">Uvicorn's `ProxyHeadersMiddleware`</a> |
||||
|
* <a href="https://github.com/florimondmanca/msgpack-asgi" class="external-link" target="_blank">MessagePack</a> |
||||
|
|
||||
|
Чтобы увидеть другие доступные middlewares, ознакомьтесь с <a href="https://www.starlette.io/middleware/" class="external-link" target="_blank">документацией по Middlewares от Starlette</a> и <a href="https://github.com/florimondmanca/awesome-asgi" class="external-link" target="_blank">ASGI Awesome List</a>. |
@ -0,0 +1,186 @@ |
|||||
|
# OpenAPI Callbacks |
||||
|
|
||||
|
Вы можете создать API с *операцией пути*, которая может инициировать запрос к *внешнему API*, созданному кем-то другим (возможно, тем же разработчиком, который будет *использовать* ваше API). |
||||
|
|
||||
|
Процесс, который происходит, когда ваше API-приложение вызывает *внешний API*, называется "обратным вызовом" (callback). Потому что программа, написанная внешним разработчиком, отправляет запрос в ваше API, а затем ваше API делает "обратный вызов", отправляя запрос во *внешний API* (который, вероятно, был создан тем же разработчиком). |
||||
|
|
||||
|
В этом случае вам может понадобиться документировать, как этот внешний API *должен* выглядеть. Какие *операции пути* он должен иметь, какое тело ожидать, какой ответ должен возвращать и т. д. |
||||
|
|
||||
|
## Приложение с обратными вызовами |
||||
|
|
||||
|
Давайте посмотрим на всё это на примере. |
||||
|
|
||||
|
Представьте, что вы разрабатываете приложение, которое позволяет создавать счета-фактуры. |
||||
|
|
||||
|
Эти счета-фактуры будут иметь `id`, `title` (необязательно), `customer` и `total`. |
||||
|
|
||||
|
Пользователь вашего API (внешний разработчик) создаст счет-фактуру в вашем API с помощью POST-запроса. |
||||
|
|
||||
|
Затем ваше API (давайте представим): |
||||
|
|
||||
|
* Отправит счет некоторому клиенту внешнего разработчика. |
||||
|
* Соберет деньги. |
||||
|
* Отправит уведомление обратно пользователю API (внешнему разработчику). |
||||
|
* Это будет сделано путем отправки POST-запроса (из *вашего API*) на какой-то *внешний API*, предоставленный тем внешним разработчиком (это и есть "обратный вызов"). |
||||
|
|
||||
|
## Обычное приложение **FastAPI** |
||||
|
|
||||
|
Давайте сначала посмотрим, как будет выглядеть обычное API-приложение до добавления обратного вызова. |
||||
|
|
||||
|
Оно будет иметь *операцию пути*, которая будет получать тело `Invoice`, и параметр запроса `callback_url`, который будет содержать URL для обратного вызова. |
||||
|
|
||||
|
Эта часть довольно обычная, большая часть кода, вероятно, уже знакома вам: |
||||
|
|
||||
|
{* ../../docs_src/openapi_callbacks/tutorial001.py hl[9:13,36:53] *} |
||||
|
|
||||
|
/// tip | Совет |
||||
|
|
||||
|
Параметр запроса `callback_url` использует тип Pydantic <a href="https://docs.pydantic.dev/latest/api/networks/" class="external-link" target="_blank">Url</a>. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
Единственное, что ново, это `callbacks=invoices_callback_router.routes` в качестве аргумента для декоратора *операции пути*. Мы посмотрим, что это такое дальше. |
||||
|
|
||||
|
## Документирование обратного вызова |
||||
|
|
||||
|
Фактический код обратного вызова будет сильно зависеть от вашего собственного 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>. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Написание кода документации для обратного вызова |
||||
|
|
||||
|
Этот код не будет выполняться в вашем приложении, нам он нужен только для того, чтобы *документировать*, как этот *внешний API* должен выглядеть. |
||||
|
|
||||
|
Но вы уже знаете, как легко создать автоматическую документацию для API с использованием **FastAPI**. |
||||
|
|
||||
|
Итак, мы собираемся использовать эти знания, чтобы задокументировать, как *внешний API* должен выглядеть... создав *операции пути*, которые внешний API должен реализовать (те, которые ваше API будет вызывать). |
||||
|
|
||||
|
/// tip | Совет |
||||
|
|
||||
|
При написании кода для документирования обратного вызова может быть полезно представить, что вы — это тот *внешний разработчик*. И что вы в данный момент реализуете *внешний API*, а не *ваше API*. |
||||
|
|
||||
|
Временное принятие этой точки зрения (внешнего разработчика) может помочь вам почувствовать, что более очевидно, где разместить параметры, модель Pydantic для тела, для ответа и т. д. для этого *внешнего API*. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
### Создание `APIRouter` для обратного вызова |
||||
|
|
||||
|
Сначала создайте новый `APIRouter`, который будет содержать один или несколько обратных вызовов. |
||||
|
|
||||
|
{* ../../docs_src/openapi_callbacks/tutorial001.py hl[3,25] *} |
||||
|
|
||||
|
### Создание *операции пути* для обратного вызова |
||||
|
|
||||
|
Чтобы создать *операцию пути* для обратного вызова, используйте тот же `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> (см. ниже), где могут использоваться переменные с параметрами и частями исходного запроса, отправленного в *ваше API*. |
||||
|
|
||||
|
### Выражение пути обратного вызова |
||||
|
|
||||
|
*Путь* обратного вызова может содержать <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 (внешний разработчик) отправляет запрос в *ваше 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, полученный в виде параметра запроса в `callback_url` (`https://www.external.org/events`) и также `id` счета-фактуры из JSON-тела (`2expen51ve`). |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
### Добавление маршрутизатора для обратных вызовов |
||||
|
|
||||
|
На этом этапе у вас есть нужные *операции пути для обратных вызовов* (те, которые *внешний разработчик* должен реализовать во *внешнем API*) в созданном выше маршрутизаторе для обратных вызовов. |
||||
|
|
||||
|
Теперь используйте параметр `callbacks` в *декораторе операции пути вашего API*, чтобы передать атрибут `.routes` (на самом деле это просто `list` маршрутов/*операций путей*) из этого маршрутизатора для обратных вызовов: |
||||
|
|
||||
|
{* ../../docs_src/openapi_callbacks/tutorial001.py hl[35] *} |
||||
|
|
||||
|
/// tip | Совет |
||||
|
|
||||
|
Обратите внимание, что вы передаете не сам маршрутизатор (`invoices_callback_router`) в `callback=`, а атрибут `.routes`, как в `invoices_callback_router.routes`. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
### Проверка документации |
||||
|
|
||||
|
Теперь вы можете запустить своё приложение и перейти на <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 Вебхуки |
||||
|
|
||||
|
Существуют случаи, когда вы хотите сообщить вашим **пользователям API**, что ваше приложение может вызвать *их* приложение (отправив запрос) с некоторыми данными, обычно чтобы **уведомить** о каком-то типе **события**. |
||||
|
|
||||
|
Это означает, что вместо обычного процесса, когда ваши пользователи отправляют запросы вашему API, это **ваш API** (или ваше приложение), который может **отправлять запросы их системе** (их API, их приложению). |
||||
|
|
||||
|
Обычно это называется **вебхук**. |
||||
|
|
||||
|
## Шаги вебхуков |
||||
|
|
||||
|
Процесс обычно заключается в том, что **вы определяете** в своем коде, какое сообщение вы будете отправлять, **тело запроса**. |
||||
|
|
||||
|
Вы также каким-то образом определяете, в какие **моменты** ваше приложение будет отправлять эти запросы или события. |
||||
|
|
||||
|
А **ваши пользователи** каким-то образом (например, в веб-интерфейсе где-то) определяют **URL**, на который ваше приложение должно отправлять эти запросы. |
||||
|
|
||||
|
Вся **логика** относительно того, как регистрировать URL для вебхуков и писать код для отправки этих запросов, зависит от вас. Вы пишете это так, как хотите, в **вашем собственном коде**. |
||||
|
|
||||
|
## Документирование вебхуков с **FastAPI** и OpenAPI |
||||
|
|
||||
|
С помощью **FastAPI**, используя OpenAPI, вы можете определить имена этих вебхуков, типы HTTP операций, которые ваше приложение может отправлять (например, `POST`, `PUT` и т.д.), и тела **запросов**, которые ваше приложение будет отправлять. |
||||
|
|
||||
|
Это может значительно облегчить вашим пользователям **реализацию их API** для приема ваших **вебхуков**, они даже могут автоматически сгенерировать часть своего собственного кода API. |
||||
|
|
||||
|
/// info | Информация |
||||
|
|
||||
|
Вебхуки доступны в OpenAPI 3.1.0 и выше, поддерживаются FastAPI `0.99.0` и выше. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Приложение с вебхуками |
||||
|
|
||||
|
Когда вы создаете приложение **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**, куда они хотят получать запрос вебхука другим способом (например, через веб-интерфейс). |
||||
|
|
||||
|
### Проверьте документацию |
||||
|
|
||||
|
Теперь вы можете запустить ваше приложение и перейти по ссылке <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 @@ |
|||||
|
# Расширенная конфигурация операций пути |
||||
|
|
||||
|
## OpenAPI operationId |
||||
|
|
||||
|
/// warning | Предупреждение |
||||
|
|
||||
|
Если вы не "эксперт" в OpenAPI, вам, вероятно, это не нужно. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
Вы можете установить OpenAPI `operationId` для использования в вашей *операции пути* с параметром `operation_id`. |
||||
|
|
||||
|
Вам нужно будет убедиться, что он уникален для каждой операции. |
||||
|
|
||||
|
{* ../../docs_src/path_operation_advanced_configuration/tutorial001.py hl[6] *} |
||||
|
|
||||
|
### Использование имени функции *операции пути* как 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 |
||||
|
|
||||
|
Чтобы исключить *операцию пути* из создаваемой схемы OpenAPI (и, следовательно, из систем автоматической документации), используйте параметр `include_in_schema` и установите его в `False`: |
||||
|
|
||||
|
{* ../../docs_src/path_operation_advanced_configuration/tutorial003.py hl[6] *} |
||||
|
|
||||
|
## Расширенное описание из docstring |
||||
|
|
||||
|
Вы можете ограничить строки, используемые из docstring функции *операции пути* для OpenAPI. |
||||
|
|
||||
|
Добавление `\f` (экранированный символ "form feed") приводит к тому, что **FastAPI** обрезает вывод, используемый для OpenAPI в этой точке. |
||||
|
|
||||
|
Это не будет отображаться в документации, но другие инструменты (такие как Sphinx) смогут использовать остальное. |
||||
|
|
||||
|
{* ../../docs_src/path_operation_advanced_configuration/tutorial004.py hl[19:29] *} |
||||
|
|
||||
|
## Дополнительные ответы |
||||
|
|
||||
|
Вы, вероятно, уже видели, как объявлять `response_model` и `status_code` для *операции пути*. |
||||
|
|
||||
|
Это определяет метаданные о основном ответе *операции пути*. |
||||
|
|
||||
|
Вы также можете объявить дополнительные ответы с их моделями, статусами кодов и т. д. |
||||
|
|
||||
|
Здесь есть целая глава в документации об этом, вы можете прочитать ее в разделе [Дополнительные ответы в OpenAPI](additional-responses.md){.internal-link target=_blank}. |
||||
|
|
||||
|
## 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">Operation Object</a>. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
Он содержит всю информацию о *операции пути* и используется для генерации автоматической документации. |
||||
|
|
||||
|
Он включает `tags`, `parameters`, `requestBody`, `responses` и т. д. |
||||
|
|
||||
|
Эта OpenAPI схема, специфичная для *операции пути*, обычно генерируется автоматически **FastAPI**, но вы также можете расширить ее. |
||||
|
|
||||
|
/// tip | Совет |
||||
|
|
||||
|
Это низкоуровневая точка расширения. |
||||
|
|
||||
|
Если вам нужно только объявить дополнительные ответы, более удобный способ сделать это — с [Дополнительные ответы в OpenAPI](additional-responses.md){.internal-link target=_blank}. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
Вы можете расширить схему OpenAPI для *операции пути* с помощью параметра `openapi_extra`. |
||||
|
|
||||
|
### Расширения OpenAPI |
||||
|
|
||||
|
Этот `openapi_extra` может быть полезен, например, для объявления [OpenAPI Extensions](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 схема *операции пути* |
||||
|
|
||||
|
Словарь в `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 |
||||
|
|
||||
|
Используя эту же хитрость, вы могли бы использовать модель 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 @@ |
|||||
|
# Заголовки ответов |
||||
|
|
||||
|
## Используйте параметр `Response` |
||||
|
|
||||
|
Вы можете объявить параметр типа `Response` в вашей *функции обработки пути* (как вы можете сделать для cookies). |
||||
|
|
||||
|
А затем вы можете установить заголовки в этом *временном* объекте ответа. |
||||
|
|
||||
|
{* ../../docs_src/response_headers/tutorial002.py hl[1, 7:8] *} |
||||
|
|
||||
|
И затем вы можете вернуть любой объект, который вам нужен, как вы обычно делаете (например, `dict`, модель базы данных и т.д.). |
||||
|
|
||||
|
И если вы объявили `response_model`, он все равно будет использован для фильтрации и конвертации объекта, который вы вернули. |
||||
|
|
||||
|
**FastAPI** использует этот *временный* ответ для извлечения заголовков (а также cookies и кода состояния) и помещает их в окончательный ответ, содержащий значение, которое вы вернули, отфильтрованное с помощью любого `response_model`. |
||||
|
|
||||
|
Вы также можете объявить параметр `Response` в зависимостях и установить заголовки (и cookies) в них. |
||||
|
|
||||
|
## Возврат `Response` напрямую |
||||
|
|
||||
|
Вы также можете добавить заголовки, когда возвращаете `Response` напрямую. |
||||
|
|
||||
|
Создайте ответ, как описано в разделе [Return a Response Directly](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` может часто использоваться для установки заголовков и cookies, **FastAPI** также предоставляет его в `fastapi.Response`. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Пользовательские заголовки |
||||
|
|
||||
|
Имейте в виду, что пользовательские проприетарные заголовки могут быть добавлены <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 Базовая Аутентификация |
||||
|
|
||||
|
Для самых простых случаев вы можете использовать HTTP Базовую Аутентификацию. |
||||
|
|
||||
|
В HTTP Базовой Аутентификации, приложение ожидает заголовок, содержащий имя пользователя и пароль. |
||||
|
|
||||
|
Если оно не получает его, то возвращает ошибку HTTP 401 "Unauthorized". |
||||
|
|
||||
|
Также возвращается заголовок `WWW-Authenticate` со значением `Basic` и необязательный параметр `realm`. |
||||
|
|
||||
|
Это сообщает браузеру показать встроенное окно запроса имени пользователя и пароля. |
||||
|
|
||||
|
Затем, когда вы вводите это имя пользователя и пароль, браузер отправляет их в заголовке автоматически. |
||||
|
|
||||
|
## Простая HTTP Базовая Аутентификация |
||||
|
|
||||
|
* Импортируйте `HTTPBasic` и `HTTPBasicCredentials`. |
||||
|
* Создайте "`сущность` схемы безопасности" с использованием `HTTPBasic`. |
||||
|
* Используйте эту `схему безопасности` с зависимостью в вашем *путевом обработчике*. |
||||
|
* Это возвращает объект типа `HTTPBasicCredentials`: |
||||
|
* Он содержит `username` и `password`, которые были отправлены. |
||||
|
|
||||
|
{* ../../docs_src/security/tutorial006_an_py39.py hl[4,8,12] *} |
||||
|
|
||||
|
Когда вы попробуете открыть URL в первый раз (или нажмёте кнопку "Execute" в документации), браузер запросит у вас ввод имени пользователя и пароля: |
||||
|
|
||||
|
<img src="/img/tutorial/security/image12.png"> |
||||
|
|
||||
|
## Проверка имени пользователя |
||||
|
|
||||
|
Вот более полный пример. |
||||
|
|
||||
|
Используйте зависимость, чтобы проверить, правильны ли имя пользователя и пароль. |
||||
|
|
||||
|
Для этого используйте стандартный модуль 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()`, это будет безопасно от типа атак, называемых "атаки по времени". |
||||
|
|
||||
|
### Атаки по времени |
||||
|
|
||||
|
Что же такое "атака по времени"? |
||||
|
|
||||
|
Представьте, что некоторые злоумышленники пытаются угадать имя пользователя и пароль. |
||||
|
|
||||
|
И они отправляют запрос с именем пользователя `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` перед тем, как осознать, что эти две строки не одинаковы. Поэтому для возвращения "Неверное имя пользователя или пароль" потребуется немного больше времени. |
||||
|
|
||||
|
#### Время ответа помогает злоумышленникам |
||||
|
|
||||
|
В этот момент, заметив, что сервер потратил несколько микросекунд дольше для отправки ответа "Неверное имя пользователя или пароль", злоумышленники будут знать, что они угадали _что-то_ правильно, некоторые начальные буквы были правильными. |
||||
|
|
||||
|
И затем они могут попробовать снова, зная, что это, вероятно, что-то больше похожее на `stanleyjobsox`, чем на `johndoe`. |
||||
|
|
||||
|
#### "Профессиональная" атака |
||||
|
|
||||
|
Конечно, злоумышленники не будут пробовать все это вручную, они напишут программу, чтобы сделать это, возможно, с тысячами или миллионами тестов в секунду. И они получат только одну дополнительную правильную букву за раз. |
||||
|
|
||||
|
Но, делая это, за несколько минут или часов злоумышленники могут угадать правильное имя пользователя и пароль с "помощью" нашего приложения, просто используя время, взятое на ответ. |
||||
|
|
||||
|
#### Исправьте это с помощью `secrets.compare_digest()` |
||||
|
|
||||
|
Но в нашем коде мы фактически используем `secrets.compare_digest()`. |
||||
|
|
||||
|
Вкратце, это займет одинаковое время для сравнения `stanleyjobsox` с `stanleyjobson`, как и для сравнения `johndoe` с `stanleyjobson`. И то же самое для пароля. |
||||
|
|
||||
|
Таким образом, используя `secrets.compare_digest()` в вашем коде приложения, оно будет безопасным от всего этого множества атак на безопасность. |
||||
|
|
||||
|
### Возвращение ошибки |
||||
|
|
||||
|
После обнаружения, что учетные данные неверны, верните `HTTPException` со статусным кодом 401 (тем же, что возвращается, когда учетные данные не предоставлены) и добавьте заголовок `WWW-Authenticate`, чтобы заставить браузер снова показать приглашение о вводе данных: |
||||
|
|
||||
|
{* ../../docs_src/security/tutorial007_an_py39.py hl[26:30] *} |
@ -0,0 +1,19 @@ |
|||||
|
# Расширенная безопасность |
||||
|
|
||||
|
## Дополнительные функции |
||||
|
|
||||
|
Существуют дополнительные функции для обеспечения безопасности, помимо тех, которые рассмотрены в [Учебнике - Руководстве пользователя: Безопасность](../../tutorial/security/index.md){.internal-link target=_blank}. |
||||
|
|
||||
|
/// tip | Совет |
||||
|
|
||||
|
Следующие разделы **не обязательно "продвинутые"**. |
||||
|
|
||||
|
И возможно, что решение для вашего случая использования находится в одном из них. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Сначала прочитайте Учебник |
||||
|
|
||||
|
Следующие разделы предполагают, что вы уже прочитали основной [Учебник - Руководство пользователя: Безопасность](../../tutorial/security/index.md){.internal-link target=_blank}. |
||||
|
|
||||
|
Все они основаны на тех же концепциях, но позволяют добавить дополнительные функции. |
@ -0,0 +1,274 @@ |
|||||
|
# OAuth2 области |
||||
|
|
||||
|
Вы можете использовать OAuth2 области непосредственно с **FastAPI**, они интегрированы для бесшовной работы. |
||||
|
|
||||
|
Это позволит вам иметь более детализированную систему разрешений, следуя стандарту OAuth2, интегрированному в ваше приложение OpenAPI (и API-документацию). |
||||
|
|
||||
|
OAuth2 с областями — это механизм, используемый многими крупными поставщиками аутентификации, такими как Facebook, Google, GitHub, Microsoft, Twitter и др. Они используют его, чтобы предоставить пользователям и приложениям доступ к определённым разрешениям. |
||||
|
|
||||
|
Каждый раз, когда вы "входите через" Facebook, Google, GitHub, Microsoft, Twitter, это приложение использует OAuth2 с областями. |
||||
|
|
||||
|
В этом разделе вы узнаете, как управлять аутентификацией и авторизацией с помощью тех же OAuth2 областей в вашем приложении на **FastAPI**. |
||||
|
|
||||
|
/// warning | Предупреждение |
||||
|
|
||||
|
Это более или менее продвинутый раздел. Если вы только начинаете, вы можете его пропустить. |
||||
|
|
||||
|
Вам не обязательно нужны OAuth2 области, и вы можете обрабатывать аутентификацию и авторизацию так, как вам удобно. |
||||
|
|
||||
|
Но OAuth2 с областями можно красиво интегрировать в ваш API (с OpenAPI) и в вашу API-документацию. |
||||
|
|
||||
|
Тем не менее, вы всё равно соблюдаете эти области или любое другое требование безопасности/авторизации, как вам нужно, в вашем коде. |
||||
|
|
||||
|
Во многих случаях, OAuth2 с областями может быть излишен. |
||||
|
|
||||
|
Но если вы знаете, что он вам нужен, или вы просто любопытны, продолжайте читать. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## OAuth2 области и OpenAPI |
||||
|
|
||||
|
Спецификация OAuth2 определяет "области" как список строк, разделённых пробелами. |
||||
|
|
||||
|
Содержимое каждой из этих строк может иметь любой формат, но не должно содержать пробелов. |
||||
|
|
||||
|
Эти области представляют собой "разрешения". |
||||
|
|
||||
|
В OpenAPI (например, в API-документации) вы можете определить "схемы безопасности". |
||||
|
|
||||
|
Когда одна из этих схем безопасности использует OAuth2, вы также можете определить и использовать области. |
||||
|
|
||||
|
Каждая "область" — это просто строка (без пробелов). |
||||
|
|
||||
|
Они обычно используются для определения конкретных разрешений безопасности, например: |
||||
|
|
||||
|
* `users:read` или `users:write` — это общие примеры. |
||||
|
* `instagram_basic` используется Facebook / Instagram. |
||||
|
* `https://www.googleapis.com/auth/drive` используется Google. |
||||
|
|
||||
|
/// info | Информация |
||||
|
|
||||
|
В OAuth2 "область" — это просто строка, которая декларирует необходимое специфическое разрешение. |
||||
|
|
||||
|
Неважно, есть ли у неё другие символы, такие как `:`, или если это URL. |
||||
|
|
||||
|
Эти детали зависят от реализации. |
||||
|
|
||||
|
Для OAuth2 они просто строки. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Глобальный обзор |
||||
|
|
||||
|
Во-первых, быстро рассмотрим части, которые изменяются в примерах в основном **Учебнике - Руководстве пользователя** для [OAuth2 с паролем (и хешированием), носитель с JWT токенами](../../tutorial/security/oauth2-jwt.md){.internal-link target=_blank}. Теперь используя OAuth2 области: |
||||
|
|
||||
|
{* ../../docs_src/security/tutorial005_an_py310.py hl[5,9,13,47,65,106,108:116,122:125,129:135,140,156] *} |
||||
|
|
||||
|
Теперь давайте рассмотрим эти изменения шаг за шагом. |
||||
|
|
||||
|
## Схема безопасности OAuth2 |
||||
|
|
||||
|
Первое изменение заключается в том, что теперь мы объявляем схему безопасности OAuth2 с двумя доступными областями, `me` и `items`. |
||||
|
|
||||
|
Параметр `scopes` принимает `dict` с каждой областью в качестве ключа и описанием в качестве значения: |
||||
|
|
||||
|
{* ../../docs_src/security/tutorial005_an_py310.py hl[63:66] *} |
||||
|
|
||||
|
Поскольку мы теперь объявляем эти области, они будут отображаться в API-документации, когда вы войдете/авторизуетесь. |
||||
|
|
||||
|
И вы сможете выбрать, к каким областям вы хотите предоставить доступ: `me` и `items`. |
||||
|
|
||||
|
Это тот же механизм, который используется при предоставлении разрешений при входе через Facebook, Google, GitHub и т.д.: |
||||
|
|
||||
|
<img src="/img/tutorial/security/image11.png"> |
||||
|
|
||||
|
## JWT токен с областями |
||||
|
|
||||
|
Теперь измените *path operation*, чтобы вернуть запрошенные области. |
||||
|
|
||||
|
Мы всё ещё используем тот же `OAuth2PasswordRequestForm`. Он включает собственность `scopes` со `списком` `str`, с каждой областью, полученной в запросе. |
||||
|
|
||||
|
И мы возвращаем области как часть JWT токена. |
||||
|
|
||||
|
/// danger | Опасность |
||||
|
|
||||
|
Для простоты, здесь мы просто добавляем области, полученные напрямую в токен. |
||||
|
|
||||
|
Но в вашем приложении, для безопасности, вы должны убедиться, что добавляете только те области, которые пользователь действительно может иметь, или те, которые вы предопределили. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
{* ../../docs_src/security/tutorial005_an_py310.py hl[156] *} |
||||
|
|
||||
|
## Объявление областей в *path operations* и зависимостях |
||||
|
|
||||
|
Теперь мы объявляем, что *path operation* для `/users/me/items/` требует область `items`. |
||||
|
|
||||
|
Для этого мы импортируем и используем `Security` из `fastapi`. |
||||
|
|
||||
|
Вы можете использовать `Security` для объявления зависимостей (так же, как `Depends`), но `Security` также получает параметр `scopes` со списком областей (строк). |
||||
|
|
||||
|
В этом случае мы передаем функцию зависимости `get_current_active_user` в `Security` (так же, как мы бы делали с `Depends`). |
||||
|
|
||||
|
Но мы также передаем `список` областей, в этом случае только одну область: `items` (их могло бы быть больше). |
||||
|
|
||||
|
И функция зависимости `get_current_active_user` также может объявлять подзависимости, не только с помощью `Depends`, но и с помощью `Security`. Объявляя свою подзависимую функцию (`get_current_user`), и дополнительные требования к областям. |
||||
|
|
||||
|
В этом случае требуется область `me` (могло бы требоваться более одной области). |
||||
|
|
||||
|
/// note | Примечание |
||||
|
|
||||
|
Вам не обязательно нужно добавлять разные области в разных местах. |
||||
|
|
||||
|
Мы делаем это здесь, чтобы продемонстрировать, как **FastAPI** обрабатывает области, объявленные на разных уровнях. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
{* ../../docs_src/security/tutorial005_an_py310.py hl[5,140,171] *} |
||||
|
|
||||
|
/// info | Технические детали |
||||
|
|
||||
|
`Security` на самом деле является подклассом `Depends`, и имеет только один дополнительный параметр, который мы увидим позже. |
||||
|
|
||||
|
Но используя `Security` вместо `Depends`, **FastAPI** распознает, что можно объявлять области безопасности, использовать их внутри и документировать API с помощью OpenAPI. |
||||
|
|
||||
|
Но когда вы импортируете `Query`, `Path`, `Depends`, `Security` и другие из `fastapi`, это на самом деле функции, которые возвращают специальные классы. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Использование `SecurityScopes` |
||||
|
|
||||
|
Теперь обновите зависимость `get_current_user`. |
||||
|
|
||||
|
Эта функция используется приведёнными выше зависимостями. |
||||
|
|
||||
|
Здесь мы используем ту же схему OAuth2, которую создали ранее, объявляя её как зависимость: `oauth2_scheme`. |
||||
|
|
||||
|
Поскольку эта функция зависимости сама по себе не имеет требований к областям, мы можем использовать `Depends` с `oauth2_scheme`, нам не нужно использовать `Security`, когда нам не нужно указывать области безопасности. |
||||
|
|
||||
|
Мы также объявляем специальный параметр типа `SecurityScopes`, импортированный из `fastapi.security`. |
||||
|
|
||||
|
Этот класс `SecurityScopes` аналогичен `Request` (в `Request` использовался объект запроса напрямую). |
||||
|
|
||||
|
{* ../../docs_src/security/tutorial005_an_py310.py hl[9,106] *} |
||||
|
|
||||
|
## Используйте `scopes` |
||||
|
|
||||
|
Параметр `security_scopes` будет типа `SecurityScopes`. |
||||
|
|
||||
|
Он будет иметь свойство `scopes` со списком всех областей, требуемых самой функцией и всеми зависимостями, использующими её как подзависимость. Это значит, все "зависящие"... это может звучать запутанно, ниже объясняется подробнее. |
||||
|
|
||||
|
Объект `security_scopes` (класса `SecurityScopes`) также предоставляет атрибут `scope_str` с одной строкой, содержащей эти области, разделённые пробелами (мы его используем). |
||||
|
|
||||
|
Мы создаём `HTTPException`, который можем использовать (`raise`) позже в некоторых точках. |
||||
|
|
||||
|
В этом исключении мы включаем требуемые области (если они есть) в виде строки, разделённой пробелами (используя `scope_str`). Мы помещаем эту строку, содержащую области, в заголовок `WWW-Authenticate` (это часть спецификации). |
||||
|
|
||||
|
{* ../../docs_src/security/tutorial005_an_py310.py hl[106,108:116] *} |
||||
|
|
||||
|
## Проверка `username` и формы данных |
||||
|
|
||||
|
Мы проверяем, что получили `username`, и извлекаем области. |
||||
|
|
||||
|
А затем мы валидируем эти данные с помощью модели Pydantic (отлавливая исключение `ValidationError`), и если у нас есть ошибка чтения JWT токена или валидирования данных с помощью Pydantic, мы возбуждаем созданное ранее `HTTPException`. |
||||
|
|
||||
|
Для этого мы обновляем модель Pydantic `TokenData`, добавив новое свойство `scopes`. |
||||
|
|
||||
|
Валидируя данные с помощью Pydantic, мы можем убедиться, что у нас есть, например, именно `список` `str` с областями и `str` с `username`. |
||||
|
|
||||
|
Вместо, например, `dict`, или чего-то ещё, что могло бы в какой-то момент сломать приложение, создавая риск безопасности. |
||||
|
|
||||
|
Мы также проверяем, что у нас есть пользователь с этим именем пользователя, и если нет, мы возбуждаем то же исключение, которое создали ранее. |
||||
|
|
||||
|
{* ../../docs_src/security/tutorial005_an_py310.py hl[47,117:128] *} |
||||
|
|
||||
|
## Проверка `scopes` |
||||
|
|
||||
|
Теперь проверяем, что все области, требуемые этой зависимостью и всеми зависимыми (включая *path operations*), включены в области, предоставленные в полученном токене, в противном случае возбуждаем `HTTPException`. |
||||
|
|
||||
|
Для этого используем `security_scopes.scopes`, который содержит `список` со всеми этими областями как `str`. |
||||
|
|
||||
|
{* ../../docs_src/security/tutorial005_an_py310.py hl[129:135] *} |
||||
|
|
||||
|
## Дерево зависимостей и области |
||||
|
|
||||
|
Давайте снова рассмотрим это дерево зависимостей и области. |
||||
|
|
||||
|
Поскольку зависимость `get_current_active_user` имеет подзависимость `get_current_user`, область `"me"`, объявленная в `get_current_active_user`, будет включена в `список` требуемых областей в `security_scopes.scopes`, переданный `get_current_user`. |
||||
|
|
||||
|
*Path operation* также объявляет область, `"items"`, так что это тоже будет в `списке` `security_scopes.scopes`, переданный `get_current_user`. |
||||
|
|
||||
|
Вот как выглядит иерархия зависимостей и областей: |
||||
|
|
||||
|
* *Path operation* `read_own_items` имеет: |
||||
|
* Требуемые области `["items"]` с зависимостью: |
||||
|
* `get_current_active_user`: |
||||
|
* Функция зависимости `get_current_active_user` имеет: |
||||
|
* Требуемые области `["me"]` с зависимостью: |
||||
|
* `get_current_user`: |
||||
|
* Функция зависимости `get_current_user` имеет: |
||||
|
* Нет требуемых собственной области. |
||||
|
* Зависимость, использующую `oauth2_scheme`. |
||||
|
* Параметр `security_scopes` типа `SecurityScopes`: |
||||
|
* Этот параметр `security_scopes` имеет свойство `scopes` с `списком`, содержащим все выше указанные области, так что: |
||||
|
* `security_scopes.scopes` будет содержать `["me", "items"]` для *path operation* `read_own_items`. |
||||
|
* `security_scopes.scopes` будет содержать `["me"]` для *path operation* `read_users_me`, потому что она объявлена в зависимости `get_current_active_user`. |
||||
|
* `security_scopes.scopes` будет содержать `[]` (ничего) для *path operation* `read_system_status`, потому что она не объявляет никакой `Security` с `scopes`, а её зависимость `get_current_user` также не объявляет никаких `scopes`. |
||||
|
|
||||
|
/// tip | Подсказка |
||||
|
|
||||
|
Самое важное и "магическое" здесь то, что `get_current_user` будет иметь различный список `scopes` для проверки для каждого *path operation*. |
||||
|
|
||||
|
Всё зависит от `scopes`, объявленных в каждом *path operation* и каждой зависимости в дереве зависимостей для этого конкретного *path operation*. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Больше деталей об `SecurityScopes` |
||||
|
|
||||
|
Вы можете использовать `SecurityScopes` в любой точке, и в нескольких местах, не обязательно на "корневой" зависимости. |
||||
|
|
||||
|
Оно всегда будет иметь области безопасности, объявленные в текущих `Security` зависимостях и все зависимые для **этого конкретного** *path operation* и **этого конкретного** дерева зависимостей. |
||||
|
|
||||
|
Поскольку `SecurityScopes` будут иметь все области, объявленные зависимыми, вы можете использовать его для проверки, что токен имеет требуемые области в центральной функции зависимости, а затем объявить различные требования к областям в различных *path operations*. |
||||
|
|
||||
|
Они будут проверяться независимо для каждого *path operation*. |
||||
|
|
||||
|
## Проверьте это |
||||
|
|
||||
|
Если откроете API-документацию, вы можете пройти аутентификацию и указать, какие области хотите авторизовать. |
||||
|
|
||||
|
<img src="/img/tutorial/security/image11.png"> |
||||
|
|
||||
|
Если вы не выберете ни одной области, вы будете "аутентифицированы", но при попытке получить доступ к `/users/me/` или `/users/me/items/` вы получите ошибку, говоря, что у вас недостаточно разрешений. Вы всё ещё сможете получить доступ к `/status/`. |
||||
|
|
||||
|
И если вы выберете область `me`, но не `items`, вы сможете получить доступ к `/users/me/`, но не к `/users/me/items/`. |
||||
|
|
||||
|
Вот что произойдёт с приложениями третьих сторон, которые пытались бы получить доступ к одной из этих *path operations* с токеном, предоставленным пользователем, в зависимости от того, сколько разрешений пользователь дал приложению. |
||||
|
|
||||
|
## О сторонних интеграциях |
||||
|
|
||||
|
В этом примере мы используем "парольный" поток OAuth2. |
||||
|
|
||||
|
Это уместно, когда мы входим в наше собственное приложение, вероятно, с нашим собственным интерфейсом. |
||||
|
|
||||
|
Потому что мы можем доверять ему получение `username` и `password`, так как мы его контролируем. |
||||
|
|
||||
|
Но если вы создаёте OAuth2 приложение, к которому будут подключаться другие (то есть, если вы создаёте поставщика аутентификации, эквивалентного Facebook, Google, GitHub и т.д.), вы должны использовать один из других потоков. |
||||
|
|
||||
|
Наиболее распространенный — это неявный поток. |
||||
|
|
||||
|
Наиболее безопасный — это поток с кодом, но его сложнее реализовать, так как он требует больше шагов. Из-за сложности многие провайдеры в конечном итоге рекомендуют неявный поток. |
||||
|
|
||||
|
/// note | Примечание |
||||
|
|
||||
|
Часто каждый поставщик аутентификации называет свои потоки по-разному, чтобы сделать это частью их бренда. |
||||
|
|
||||
|
Но в конце концов, они реализуют тот же стандарт OAuth2. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
**FastAPI** включает утилиты для всех этих потоков аутентификации OAuth2 в `fastapi.security.oauth2`. |
||||
|
|
||||
|
## `Security` в бедператора `dependencies` |
||||
|
|
||||
|
Так же, как вы можете определить `список` `Depends` в параметре `dependencies` там бедператора (как объясняется в [Зависимости в бедператорaх path operation](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), вы также можете использовать `Security` с `scopes` там. |
@ -0,0 +1,346 @@ |
|||||
|
# Настройки и переменные окружения |
||||
|
|
||||
|
Во многих случаях вашему приложению могут потребоваться внешние настройки или конфигурации, например, секретные ключи, учетные данные для базы данных, учетные данные для сервисов электронной почты и так далее. |
||||
|
|
||||
|
Большинство из этих настроек переменные (могут изменяться), как, например, URL базы данных. И многие могут быть конфиденциальными, как секреты. |
||||
|
|
||||
|
По этой причине их обычно предоставляют в переменных окружения, которые считываются приложением. |
||||
|
|
||||
|
/// tip | Совет |
||||
|
|
||||
|
Чтобы понять переменные окружения, вы можете прочитать [Переменные окружения](../environment-variables.md){.internal-link target=_blank}. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Типы и валидация |
||||
|
|
||||
|
Эти переменные окружения могут обрабатывать только текстовые строки, так как они находятся вне Python и должны быть совместимы с другими программами и остальной системой (и даже с различными операционными системами, такими как Linux, Windows, macOS). |
||||
|
|
||||
|
Это значит, что любое значение, считанное в Python из переменной окружения, будет `str`, и любая конвертация в другой тип или любая валидация должна выполняться в коде. |
||||
|
|
||||
|
## Pydantic `Settings` |
||||
|
|
||||
|
К счастью, Pydantic предоставляет отличную утилиту для работы с этими настройками, поступающими из переменных окружения, с помощью <a href="https://docs.pydantic.dev/latest/concepts/pydantic_settings/" class="external-link" target="_blank">Pydantic: Управление настройками</a>. |
||||
|
|
||||
|
### Установка `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` |
||||
|
|
||||
|
Импортируйте `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` |
||||
|
|
||||
|
Затем вы можете использовать новый объект `settings` в вашем приложении: |
||||
|
|
||||
|
{* ../../docs_src/settings/tutorial001.py hl[18:20] *} |
||||
|
|
||||
|
### Запуск сервера |
||||
|
|
||||
|
Затем вы бы запустили сервер, передавая конфигурации в качестве переменных окружения, например, вы могли бы установить `ADMIN_EMAIL` и `APP_NAME` с помощью: |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
$ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.py |
||||
|
|
||||
|
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) |
||||
|
``` |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
/// tip | Совет |
||||
|
|
||||
|
Чтобы установить несколько переменных окружения для одной команды, просто разделите их пробелом и укажите все перед командой. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
И затем `admin_email` будет установлен на `"deadpool@example.com"`. |
||||
|
|
||||
|
`app_name` будет `"ChimichangApp"`. |
||||
|
|
||||
|
А `items_per_user` сохранит свое значение по умолчанию `50`. |
||||
|
|
||||
|
## Настройки в другом модуле |
||||
|
|
||||
|
Вы можете поместить эти настройки в другом файле модуля, как вы видели в [Большие приложения - Несколько файлов](../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`, который используется везде. |
||||
|
|
||||
|
Это может быть особенно полезно во время тестирования, поскольку очень легко переопределить зависимость с вашими собственными настраиваемыми настройками. |
||||
|
|
||||
|
### Файл конфигурации |
||||
|
|
||||
|
Из предыдущего примера, ваш файл `config.py` может выглядеть так: |
||||
|
|
||||
|
{* ../../docs_src/settings/app02/config.py hl[10] *} |
||||
|
|
||||
|
Обратите внимание, что теперь мы не создаем экземпляр по умолчанию `settings = Settings()`. |
||||
|
|
||||
|
### Основной файл приложения |
||||
|
|
||||
|
Теперь мы создаем зависимость, которая возвращает новый `config.Settings()`. |
||||
|
|
||||
|
{* ../../docs_src/settings/app02_an_py39/main.py hl[6,12:13] *} |
||||
|
|
||||
|
/// tip | Совет |
||||
|
|
||||
|
Мы обсудим `@lru_cache` немного позже. |
||||
|
|
||||
|
Пока что вы можете предположить, что `get_settings()` — это обычная функция. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
И затем мы можем требовать его из функции обработчика *path operation* в качестве зависимости и использовать его везде, где это необходимо. |
||||
|
|
||||
|
{* ../../docs_src/settings/app02_an_py39/main.py hl[17,19:21] *} |
||||
|
|
||||
|
### Настройки и тестирование |
||||
|
|
||||
|
Затем будет очень легко предоставить другой объект настроек во время тестирования, создав переопределение зависимости для `get_settings`: |
||||
|
|
||||
|
{* ../../docs_src/settings/app02/test_main.py hl[9:10,13,21] *} |
||||
|
|
||||
|
В переопределении зависимости мы устанавливаем новое значение для `admin_email` при создании нового объекта `Settings`, а затем возвращаем этот новый объект. |
||||
|
|
||||
|
Затем мы можем протестировать, что он используется. |
||||
|
|
||||
|
## Чтение файла `.env` |
||||
|
|
||||
|
Если у вас много настроек, которые, возможно, часто меняются, может быть полезно поместить их в файл и затем считать их как переменные окружения. |
||||
|
|
||||
|
Эта практика настолько распространена, что у нее есть название, эти переменные окружения обычно размещаются в файл `.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) support</a>. |
||||
|
|
||||
|
/// tip | Совет |
||||
|
|
||||
|
Для этого нужно `pip install python-dotenv`. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
### Файл `.env` |
||||
|
|
||||
|
Вы могли бы иметь файл `.env` с: |
||||
|
|
||||
|
```bash |
||||
|
ADMIN_EMAIL="deadpool@example.com" |
||||
|
APP_NAME="ChimichangApp" |
||||
|
``` |
||||
|
|
||||
|
### Чтение настроек из `.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`, и чтобы получить автозаполнение и inline ошибки, вы можете импортировать и использовать `SettingsConfigDict` для определения этой `dict`. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
Здесь мы определяем конфигурацию `env_file` внутри класса Pydantic `Settings` и устанавливаем значение на имя файла с файлом dotenv, который мы хотим использовать. |
||||
|
|
||||
|
### Создание `Settings` только один раз с помощью `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` модифицирует функцию, которую он декорирует, чтобы возвращать то же значение, которое было возвращено в первый раз, вместо того, чтобы вычислять его заново, выполняя код функции каждый раз. |
||||
|
|
||||
|
Таким образом, функция ниже будет выполняться один раз для каждой комбинации аргументов. А затем значения, возвращенные каждой из этих комбинаций аргументов, будут использоваться снова и снова, когда функция вызывается с точно такой же комбинацией аргументов. |
||||
|
|
||||
|
Например, если у вас есть функция: |
||||
|
|
||||
|
```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>. |
||||
|
|
||||
|
## Резюме |
||||
|
|
||||
|
Вы можете использовать Pydantic Settings для обработки настроек или конфигураций вашего приложения, с всей мощью моделей Pydantic. |
||||
|
|
||||
|
* С помощью зависимости вы можете упростить тестирование. |
||||
|
* Вы можете использовать `.env` файлы вместе с ним. |
||||
|
* Использование `@lru_cache` позволяет избежать повторного чтения dotenv файла для каждого запроса, при этом позволяя его переопределение во время тестирования. |
@ -0,0 +1,67 @@ |
|||||
|
# Подприложения - Монтирование |
||||
|
|
||||
|
Если вам нужно иметь два независимых приложения FastAPI, каждое со своей независимой OpenAPI и собственным интерфейсом документации, вы можете создать основное приложение и "монтировать" одно (или несколько) подпрограмм. |
||||
|
|
||||
|
## Монтирование приложения **FastAPI** |
||||
|
|
||||
|
"Монтирование" означает добавление полностью "независимого" приложения в определенный путь, которое будет обрабатывать все запросы, поступающие по этому пути, с _operations path_, объявленными в этом подприложении. |
||||
|
|
||||
|
### Приложение верхнего уровня |
||||
|
|
||||
|
Сначала создайте главное приложение верхнего уровня **FastAPI** и его *operations path*: |
||||
|
|
||||
|
{* ../../docs_src/sub_applications/tutorial001.py hl[3, 6:8] *} |
||||
|
|
||||
|
### Подприложение |
||||
|
|
||||
|
Затем создайте свое подприложение и его *operations path*. |
||||
|
|
||||
|
Это подприложение является просто еще одним стандартным приложением FastAPI, но именно оно будет "смонтировано": |
||||
|
|
||||
|
{* ../../docs_src/sub_applications/tutorial001.py hl[11, 14:16] *} |
||||
|
|
||||
|
### Монтирование подприложения |
||||
|
|
||||
|
В вашем приложении верхнего уровня `app` смонтируйте подприложение `subapi`. |
||||
|
|
||||
|
В этом случае оно будет смонтировано по пути `/subapi`: |
||||
|
|
||||
|
{* ../../docs_src/sub_applications/tutorial001.py hl[11, 19] *} |
||||
|
|
||||
|
### Проверка автоматической документации API |
||||
|
|
||||
|
Теперь выполните команду `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 для основного приложения, содержащую только его _operations path_: |
||||
|
|
||||
|
<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 для подприложения, содержащую только его _operations path_, все с правильным префиксом подпути `/subapi`: |
||||
|
|
||||
|
<img src="/img/tutorial/sub-applications/image02.png"> |
||||
|
|
||||
|
Если вы попробуете взаимодействовать с любым из этих двух пользовательских интерфейсов, они будут работать корректно, потому что браузер сможет общаться с каждым определенным приложением или подприложением. |
||||
|
|
||||
|
### Технические детали: `root_path` |
||||
|
|
||||
|
Когда вы монтируете подприложение, как описано выше, FastAPI позаботится о передаче пути монтирования для подприложения с использованием механизма из спецификации ASGI, называемого `root_path`. |
||||
|
|
||||
|
Таким образом, подприложение будет знать об использовании этого префикса пути для интерфейса документации. |
||||
|
|
||||
|
И подприложение также может иметь свои собственные смонтированные подприложения и все будет работать корректно, так как FastAPI автоматически обрабатывает все эти `root_path`. |
||||
|
|
||||
|
Вы узнаете больше о `root_path` и о том, как использовать его явно, в разделе о [работе за прокси](behind-a-proxy.md){.internal-link target=_blank}. |
@ -0,0 +1,126 @@ |
|||||
|
# Шаблоны |
||||
|
|
||||
|
Вы можете использовать любой механизм шаблонов с **FastAPI**. |
||||
|
|
||||
|
Распространенным выбором является Jinja2, тот же, что используется в Flask и других инструментах. |
||||
|
|
||||
|
Есть утилиты для легкой конфигурации, которые вы можете использовать непосредственно в вашем приложении **FastAPI** (предоставлено Starlette). |
||||
|
|
||||
|
## Установка зависимостей |
||||
|
|
||||
|
Убедитесь, что вы создаете [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активируйте его и установите `jinja2`: |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
$ pip install jinja2 |
||||
|
|
||||
|
---> 100% |
||||
|
``` |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
## Использование `Jinja2Templates` |
||||
|
|
||||
|
* Импортируйте `Jinja2Templates`. |
||||
|
* Создайте объект `templates`, который можно будет использовать позже. |
||||
|
* Объявите параметр `Request` в *операции маршрута*, который будет возвращать шаблон. |
||||
|
* Используйте созданные вами `templates`, чтобы рендерить и возвращать `TemplateResponse`, передавая имя шаблона, объект запроса и "контекст" — словарь с парами ключ-значение, которые будут использоваться внутри шаблона 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`. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Написание шаблонов |
||||
|
|
||||
|
Затем вы можете написать шаблон в `templates/item.html` с, например: |
||||
|
|
||||
|
```jinja hl_lines="7" |
||||
|
{!../../docs_src/templates/templates/item.html!} |
||||
|
``` |
||||
|
|
||||
|
### Контекстные значения шаблона |
||||
|
|
||||
|
В HTML, который содержит: |
||||
|
|
||||
|
{% raw %} |
||||
|
|
||||
|
```jinja |
||||
|
Item ID: {{ id }} |
||||
|
``` |
||||
|
|
||||
|
{% endraw %} |
||||
|
|
||||
|
...это отобразит `id`, взятый из "контекста" `dict`, который вы передали: |
||||
|
|
||||
|
```Python |
||||
|
{"id": id} |
||||
|
``` |
||||
|
|
||||
|
Например, с ID `42`, это отобразится как: |
||||
|
|
||||
|
```html |
||||
|
Item ID: 42 |
||||
|
``` |
||||
|
|
||||
|
### Аргументы `url_for` в шаблоне |
||||
|
|
||||
|
Вы также можете использовать `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"> |
||||
|
``` |
||||
|
|
||||
|
## Шаблоны и статичные файлы |
||||
|
|
||||
|
Вы также можете использовать `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`. |
||||
|
|
||||
|
## Подробнее |
||||
|
|
||||
|
Для получения более подробной информации, включая тестирование шаблонов, посетите <a href="https://www.starlette.io/templates/" class="external-link" target="_blank">документацию Starlette по шаблонам</a>. |
@ -0,0 +1,53 @@ |
|||||
|
# Тестирование зависимостей с переопределением |
||||
|
|
||||
|
## Переопределение зависимостей во время тестирования |
||||
|
|
||||
|
Существуют сценарии, когда вы можете захотеть переопределить зависимость во время тестирования. |
||||
|
|
||||
|
Вы не хотите, чтобы изначальная зависимость запускалась (а также любые ее подзависимости). |
||||
|
|
||||
|
Вместо этого вы хотите предоставить другую зависимость, которая будет использоваться только во время тестов (возможно, только для некоторых конкретных тестов) и будет предоставлять значение, которое можно использовать там, где использовалось значение первоначальной зависимости. |
||||
|
|
||||
|
### Примеры использования: внешний сервис |
||||
|
|
||||
|
Примером может быть наличие внешнего поставщика аутентификации, которого вам нужно вызывать. |
||||
|
|
||||
|
Вы отправляете ему токен, и он возвращает аутентифицированного пользователя. |
||||
|
|
||||
|
Этот поставщик может взимать плату за каждый запрос, и его вызов может занимать больше времени, чем если бы вы использовали заранее заданного пользователя-симулятора для тестов. |
||||
|
|
||||
|
Вы, вероятно, захотите протестировать внешнего поставщика один раз, но не обязательно вызывать его для каждого выполняемого теста. |
||||
|
|
||||
|
В этом случае вы можете переопределить зависимость, которая вызывает этого поставщика, и использовать пользовательскую зависимость, которая возвращает пользователя-симулятора, только для ваших тестов. |
||||
|
|
||||
|
### Использование атрибута `app.dependency_overrides` |
||||
|
|
||||
|
Для таких случаев ваше приложение **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,5 @@ |
|||||
|
# Тестирование событий: запуск - завершение |
||||
|
|
||||
|
Когда вам нужно, чтобы ваши обработчики событий (`startup` и `shutdown`) запускались в тестах, вы можете использовать `TestClient` вместе с оператором `with`: |
||||
|
|
||||
|
{* ../../docs_src/app_testing/tutorial003.py hl[9:12,20:24] *} |
@ -0,0 +1,13 @@ |
|||||
|
# Тестирование WebSockets |
||||
|
|
||||
|
Вы можете использовать тот же `TestClient` для тестирования WebSockets. |
||||
|
|
||||
|
Для этого вы используете `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">тестированию WebSockets</a>. |
||||
|
|
||||
|
/// |
@ -0,0 +1,56 @@ |
|||||
|
# Использование объекта Request напрямую |
||||
|
|
||||
|
До сих пор вы объявляли части запроса, которые вам нужны, с указанием их типов. |
||||
|
|
||||
|
Получение данных из: |
||||
|
|
||||
|
* Параметров пути. |
||||
|
* Заголовков. |
||||
|
* Куки. |
||||
|
* и т.д. |
||||
|
|
||||
|
И при этом **FastAPI** проверяет эти данные, конвертирует их и автоматически генерирует документацию для вашего API. |
||||
|
|
||||
|
Но бывают ситуации, когда может понадобиться доступ к объекту `Request` напрямую. |
||||
|
|
||||
|
## Подробности об объекте `Request` |
||||
|
|
||||
|
Так как **FastAPI** на самом деле является **Starlette** под капотом, с рядом различных инструментов поверх, вы можете использовать объект <a href="https://www.starlette.io/requests/" class="external-link" target="_blank">`Request`</a> от Starlette напрямую, когда это необходимо. |
||||
|
|
||||
|
Это также означает, что если вы получаете данные из объекта `Request` напрямую (например, читаете тело запроса), они не будут проверены, конвертированы или задокументированы (с помощью OpenAPI, для автоматического интерфейса пользователя API) FastAPI. |
||||
|
|
||||
|
Хотя любой другой параметр, объявленный обычным образом (например, тело с моделью Pydantic), будет проверяться, конвертироваться, аннотироваться и так далее. |
||||
|
|
||||
|
Но есть конкретные случаи, когда полезно получить объект `Request`. |
||||
|
|
||||
|
## Используйте объект `Request` напрямую |
||||
|
|
||||
|
Представьте, что вы хотите получить IP-адрес/хост клиента внутри вашей *функции операции пути*. |
||||
|
|
||||
|
Для этого вам нужно получить доступ к запросу напрямую. |
||||
|
|
||||
|
{* ../../docs_src/using_request_directly/tutorial001.py hl[1,7:8] *} |
||||
|
|
||||
|
Объявив параметр *функции операции пути* с типом `Request`, **FastAPI** поймет, что нужно передать `Request` в этот параметр. |
||||
|
|
||||
|
/// tip | Совет |
||||
|
|
||||
|
Обратите внимание, что в этом случае мы объявляем параметр пути рядом с параметром запроса. |
||||
|
|
||||
|
Таким образом, параметр пути будет извлечен, проверен, преобразован в указанный тип и аннотирован с OpenAPI. |
||||
|
|
||||
|
Аналогично, вы можете объявить любой другой параметр обычным образом, и дополнительно получить объект `Request` тоже. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Документация `Request` |
||||
|
|
||||
|
Вы можете прочитать больше подробностей об <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, другие |
||||
|
|
||||
|
Вы можете монтировать WSGI-приложения, как вы видели в [Sub Applications - Mounts](sub-applications.md){.internal-link target=_blank}, [Behind a Proxy](behind-a-proxy.md){.internal-link target=_blank}. |
||||
|
|
||||
|
Для этого вы можете использовать `WSGIMiddleware` и использовать его для обёртки вашего WSGI-приложения, например, Flask, Django и т. д. |
||||
|
|
||||
|
## Использование `WSGIMiddleware` |
||||
|
|
||||
|
Вам нужно импортировать `WSGIMiddleware`. |
||||
|
|
||||
|
Затем оберните WSGI (например, Flask) приложение с помощью middleware. |
||||
|
|
||||
|
И затем смонтируйте его под определённым путём. |
||||
|
|
||||
|
{* ../../docs_src/wsgi/tutorial001.py hl[2:3,3] *} |
||||
|
|
||||
|
## Проверка |
||||
|
|
||||
|
Теперь каждый запрос по пути `/v1/` будет обрабатываться приложением Flask. |
||||
|
|
||||
|
А остальные будут обрабатываться **FastAPI**. |
||||
|
|
||||
|
Если вы запустите его и перейдёте на <a href="http://localhost:8000/v1/" class="external-link" target="_blank">http://localhost:8000/v1/</a>, вы увидите ответ от Flask: |
||||
|
|
||||
|
```txt |
||||
|
Hello, World from Flask! |
||||
|
``` |
||||
|
|
||||
|
А если вы перейдёте на <a href="http://localhost:8000/v2" class="external-link" target="_blank">http://localhost:8000/v2</a>, вы увидите ответ от FastAPI: |
||||
|
|
||||
|
```JSON |
||||
|
{ |
||||
|
"message": "Hello World" |
||||
|
} |
||||
|
``` |
@ -0,0 +1,17 @@ |
|||||
|
# Развертывание FastAPI на облачных платформах |
||||
|
|
||||
|
Вы можете использовать практически **любого облачного провайдера** для развертывания вашего приложения на FastAPI. |
||||
|
|
||||
|
В большинстве случаев у основных облачных провайдеров есть руководства по развертыванию FastAPI с их помощью. |
||||
|
|
||||
|
## Облачные провайдеры - спонсоры |
||||
|
|
||||
|
Некоторые облачные провайдеры ✨ [**спонсируют FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, это обеспечивает продолжительное и здоровое **развитие** FastAPI и его **экосистемы**. |
||||
|
|
||||
|
И это демонстрирует их искреннюю приверженность FastAPI и его **сообществу** (вам), так как они не только хотят предоставить вам **хороший сервис**, но и убедиться, что у вас есть **хорошая и здоровая платформа**, FastAPI. 🙇 |
||||
|
|
||||
|
Возможно, вы захотите попробовать их услуги и следовать их руководствам: |
||||
|
|
||||
|
* <a href="https://docs.platform.sh/languages/python.html?utm_source=fastapi-signup&utm_medium=banner&utm_campaign=FastAPI-signup-June-2023" class="external-link" target="_blank">Platform.sh</a> |
||||
|
* <a href="https://docs.porter.run/language-specific-guides/fastapi" class="external-link" target="_blank">Porter</a> |
||||
|
* <a href="https://docs.render.com/deploy-fastapi?utm_source=deploydoc&utm_medium=referral&utm_campaign=fastapi" class="external-link" target="_blank">Render</a> |
@ -0,0 +1,139 @@ |
|||||
|
# Серверные Рабочие Процессы - Uvicorn с Рабочими Процессами |
||||
|
|
||||
|
Давайте вспомним те концепции развёртывания, которые мы обсуждали ранее: |
||||
|
|
||||
|
* Безопасность - 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 на контейнер**, но я расскажу вам о этом позже в той главе. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Несколько Рабочих Процессов |
||||
|
|
||||
|
Вы можете запустить несколько рабочих процессов с помощью опции командной строки `--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> Запуск производственного сервера 🚀 |
||||
|
|
||||
|
Поиск структуры файлов пакета в директориях с |
||||
|
<font color="#3465A4">__init__.py</font> файлами |
||||
|
Импорт из <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> Импорт объекта приложения FastAPI из модуля с |
||||
|
следующим кодом: |
||||
|
|
||||
|
<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> Используется строка импорта: <font color="#3465A4">main:app</font> |
||||
|
|
||||
|
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Сервер запущен на <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> Документация на <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000/docs</u></font> |
||||
|
|
||||
|
Логи: |
||||
|
|
||||
|
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn работает на <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000</u></font> <b>(</b>Нажмите CTRL+C для |
||||
|
выхода<b>)</b> |
||||
|
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Запущен родительский процесс <b>[</b><font color="#34E2E2"><b>27365</b></font><b>]</b> |
||||
|
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Запущен процесс сервера <b>[</b><font color="#34E2E2"><b>27368</b></font><b>]</b> |
||||
|
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Запущен процесс сервера <b>[</b><font color="#34E2E2"><b>27369</b></font><b>]</b> |
||||
|
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Запущен процесс сервера <b>[</b><font color="#34E2E2"><b>27370</b></font><b>]</b> |
||||
|
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Запущен процесс сервера <b>[</b><font color="#34E2E2"><b>27367</b></font><b>]</b> |
||||
|
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Ожидание запуска приложения. |
||||
|
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Ожидание запуска приложения. |
||||
|
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Ожидание запуска приложения. |
||||
|
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Ожидание запуска приложения. |
||||
|
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Запуск приложения завершен. |
||||
|
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Запуск приложения завершен. |
||||
|
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Запуск приложения завершен. |
||||
|
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Запуск приложения завершен. |
||||
|
``` |
||||
|
|
||||
|
</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 работает на <b>http://0.0.0.0:8080</b> (Нажмите CTRL+C для выхода) |
||||
|
<font color="#A6E22E">INFO</font>: Запущен родительский процесс [<font color="#A1EFE4"><b>27365</b></font>] |
||||
|
<font color="#A6E22E">INFO</font>: Запущен процесс сервера [<font color="#A1EFE4">27368</font>] |
||||
|
<font color="#A6E22E">INFO</font>: Ожидание запуска приложения. |
||||
|
<font color="#A6E22E">INFO</font>: Запуск приложения завершен. |
||||
|
<font color="#A6E22E">INFO</font>: Запущен процесс сервера [<font color="#A1EFE4">27369</font>] |
||||
|
<font color="#A6E22E">INFO</font>: Ожидание запуска приложения. |
||||
|
<font color="#A6E22E">INFO</font>: Запуск приложения завершен. |
||||
|
<font color="#A6E22E">INFO</font>: Запущен процесс сервера [<font color="#A1EFE4">27370</font>] |
||||
|
<font color="#A6E22E">INFO</font>: Ожидание запуска приложения. |
||||
|
<font color="#A6E22E">INFO</font>: Запуск приложения завершен. |
||||
|
<font color="#A6E22E">INFO</font>: Запущен процесс сервера [<font color="#A1EFE4">27367</font>] |
||||
|
<font color="#A6E22E">INFO</font>: Ожидание запуска приложения. |
||||
|
<font color="#A6E22E">INFO</font>: Запуск приложения завершен. |
||||
|
``` |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
Единственный новый параметр здесь - это `--workers`, который говорит Uvicorn запустить 4 рабочих процесса. |
||||
|
|
||||
|
Вы также можете видеть, что он показывает **PID** каждого процесса, `27365` для родительского процесса (это **менеджер процессов**) и один для каждого рабочего процесса: `27368`, `27369`, `27370` и `27367`. |
||||
|
|
||||
|
## Концепции Развёртывания |
||||
|
|
||||
|
Здесь вы увидели, как использовать несколько **рабочих процессов** для **параллелизации** выполнения приложения, использования преимуществ **нескольких ядер** процессора и возможности обслуживать **больше запросов**. |
||||
|
|
||||
|
Из списка концепций развёртывания, используя рабочие процессы, вы в основном поможете с частью **репликации** и немного с **перезапусками**, но вы все равно должны позаботиться о других: |
||||
|
|
||||
|
* **Безопасность - HTTPS** |
||||
|
* **Запуск при старте** |
||||
|
* ***Перезапуски*** |
||||
|
* Репликация (количество запущенных процессов) |
||||
|
* **Память** |
||||
|
* **Предыдущие шаги перед запуском** |
||||
|
|
||||
|
## Контейнеры и Docker |
||||
|
|
||||
|
В следующей главе о [FastAPI в Контейнерах - Docker](docker.md){.internal-link target=_blank} я объясню некоторые стратегии, которые вы могли бы использовать для обработки других **концепций развёртывания**. |
||||
|
|
||||
|
Я покажу вам, как **создать свое собственное изображение с нуля**, чтобы запустить единый Uvicorn процесс. Это простой процесс и, вероятно, то, что вы захотите сделать, когда используете распределенную систему управления контейнерами, такую как **Kubernetes**. |
||||
|
|
||||
|
## Итоги |
||||
|
|
||||
|
Вы можете использовать несколько рабочих процессов с помощью опции CLI `--workers` с командами `fastapi` или `uvicorn`, чтобы воспользоваться преимуществами **многоядерных процессоров**, для параллельного выполнения **нескольких процессов**. |
||||
|
|
||||
|
Вы могли бы использовать эти инструменты и идеи, если вы настраиваете **свою собственную систему развёртывания**, при этом самостоятельно заботясь о других концепциях развёртывания. |
||||
|
|
||||
|
Ознакомьтесь с следующей главой, чтобы узнать о **FastAPI** с контейнерами (например, Docker и Kubernetes). Вы увидите, что у этих инструментов есть простые способы решения других **концепций развёртывания**. ✨ |
@ -0,0 +1,56 @@ |
|||||
|
# Условный OpenAPI |
||||
|
|
||||
|
Если это необходимо, вы можете использовать настройки и переменные окружения для условной конфигурации OpenAPI в зависимости от среды и даже полностью его отключить. |
||||
|
|
||||
|
## О безопасности, API и документации |
||||
|
|
||||
|
Сокрытие пользовательских интерфейсов вашей документации в продакшене *не должно* быть способом защиты вашего API. |
||||
|
|
||||
|
Это не добавляет никакой дополнительной безопасности вашему API, *операции с путями* по-прежнему будут доступны там, где они находятся. |
||||
|
|
||||
|
Если в вашем коде есть уязвимость, она все равно будет существовать. |
||||
|
|
||||
|
Сокрытие документации просто усложняет понимание того, как взаимодействовать с вашим API, и может усложнить отладку в продакшене. Это можно считать просто формой <a href="https://en.wikipedia.org/wiki/Security_through_obscurity" class="external-link" target="_blank">безопасности через неясность</a>. |
||||
|
|
||||
|
Если вы хотите обезопасить ваш API, есть несколько более эффективных методов, например: |
||||
|
|
||||
|
* Убедитесь, что у вас есть правильно определенные Pydantic модели для тел запросов и ответов. |
||||
|
* Настройте необходимые разрешения и роли с помощью зависимостей. |
||||
|
* Никогда не храните пароли в открытом виде, только хэши паролей. |
||||
|
* Реализуйте и используйте хорошо известные криптографические инструменты, такие как Passlib и JWT токены и т. д. |
||||
|
* Добавьте более гранулированное управление разрешениями с помощью OAuth2 скоупов там, где это необходимо. |
||||
|
* ...и так далее. |
||||
|
|
||||
|
Тем не менее, у вас может быть очень специфичный случай, когда вам действительно нужно отключить документацию API для какой-то среды (например, для продакшена) или в зависимости от конфигураций из переменных окружения. |
||||
|
|
||||
|
## Условный OpenAPI из настроек и переменных окружения |
||||
|
|
||||
|
Вы можете легко использовать те же настройки Pydantic для конфигурации вашего сгенерированного OpenAPI и пользовательских интерфейсов документации. |
||||
|
|
||||
|
Например: |
||||
|
|
||||
|
{* ../../docs_src/conditional_openapi/tutorial001.py hl[6,11] *} |
||||
|
|
||||
|
Здесь мы объявляем настройку `openapi_url` с тем же значением по умолчанию `"/openapi.json"`. |
||||
|
|
||||
|
Затем мы используем её при создании приложения `FastAPI`. |
||||
|
|
||||
|
Затем вы можете отключить OpenAPI (включая UI документацию), установив переменную окружения `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> |
||||
|
|
||||
|
Затем, если вы перейдете по URL на `/openapi.json`, `/docs` или `/redoc`, вы получите ошибку `404 Not Found`, например: |
||||
|
|
||||
|
```JSON |
||||
|
{ |
||||
|
"detail": "Not Found" |
||||
|
} |
||||
|
``` |
@ -0,0 +1,70 @@ |
|||||
|
# Настройка 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. |
||||
|
|
||||
|
## Отключение подсветки синтаксиса |
||||
|
|
||||
|
Например, вы можете отключить подсветку синтаксиса в 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"> |
||||
|
|
||||
|
## Изменение темы |
||||
|
|
||||
|
Таким же образом вы можете задать тему подсветки синтаксиса с помощью ключа `"syntaxHighlight.theme"` (обратите внимание, что в нём есть точка посередине): |
||||
|
|
||||
|
{* ../../docs_src/configure_swagger_ui/tutorial002.py hl[3] *} |
||||
|
|
||||
|
Эта конфигурация изменит цветовую тему подсветки синтаксиса: |
||||
|
|
||||
|
<img src="/img/tutorial/extending-openapi/image04.png"> |
||||
|
|
||||
|
## Изменение параметров Swagger UI по умолчанию |
||||
|
|
||||
|
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 |
||||
|
|
||||
|
Чтобы увидеть все возможные конфигурации, которые вы можете использовать, прочтите официальную <a href="https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/" class="external-link" target="_blank">документацию о параметрах Swagger UI</a>. |
||||
|
|
||||
|
## Настройки только для JavaScript |
||||
|
|
||||
|
Swagger UI также позволяет настроить другие параметры как объекты **только для JavaScript** (например, функции JavaScript). |
||||
|
|
||||
|
FastAPI также включает эти настройки `presets` только для JavaScript: |
||||
|
|
||||
|
```JavaScript |
||||
|
presets: [ |
||||
|
SwaggerUIBundle.presets.apis, |
||||
|
SwaggerUIBundle.SwaggerUIStandalonePreset |
||||
|
] |
||||
|
``` |
||||
|
|
||||
|
Это объекты **JavaScript**, а не строки, поэтому вы не можете передавать их напрямую из Python-кода. |
||||
|
|
||||
|
Если вам нужно использовать конфигурации только для JavaScript, как эти, вы можете использовать один из методов, упомянутых выше. Переопределите всю *path operation* Swagger UI и вручную напишите любой необходимый JavaScript. |
@ -0,0 +1,185 @@ |
|||||
|
# Пользовательские статические ресурсы Docs UI (самостоятельный хостинг) |
||||
|
|
||||
|
API документация использует **Swagger UI** и **ReDoc**, и каждому из них требуются некоторые файлы JavaScript и CSS. |
||||
|
|
||||
|
По умолчанию эти файлы предоставляются через <abbr title="Сеть доставки контента: служба, обычно состоящая из нескольких серверов, которая предоставляет статические файлы, такие как JavaScript и CSS. Обычно используется для доставки этих файлов с сервера, наиболее близкого к клиенту, что улучшает производительность.">CDN</abbr>. |
||||
|
|
||||
|
Но возможно его настроить: можно установить определенный CDN или разместить файлы самостоятельно. |
||||
|
|
||||
|
## Пользовательский CDN для JavaScript и CSS |
||||
|
|
||||
|
Предположим, что вы хотите использовать другой <abbr title="Сеть доставки контента">CDN</abbr>, например, хотите использовать `https://unpkg.com/`. |
||||
|
|
||||
|
Это может быть полезно, если, например, вы живете в стране, где некоторые URL-адреса ограничены. |
||||
|
|
||||
|
### Отключение автоматической документации |
||||
|
|
||||
|
Первый шаг — отключить автоматическую документацию, так как по умолчанию они используют стандартный CDN. |
||||
|
|
||||
|
Чтобы отключить их, установите их URL в `None` при создании вашего приложения `FastAPI`: |
||||
|
|
||||
|
{* ../../docs_src/custom_docs_ui/tutorial001.py hl[8] *} |
||||
|
|
||||
|
### Включение пользовательской документации |
||||
|
|
||||
|
Теперь вы можете создать *path operations* для пользовательской документации. |
||||
|
|
||||
|
Вы можете повторно использовать внутренние функции 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 | Совет |
||||
|
|
||||
|
*Path operation* для `swagger_ui_redirect` является помощником, когда вы используете OAuth2. |
||||
|
|
||||
|
Если вы интегрируете свой API с провайдером OAuth2, вы сможете авторизоваться и вернуться к документации API с полученными учетными данными. И взаимодействовать с ним, используя реальную аутентификацию через OAuth2. |
||||
|
|
||||
|
Swagger UI сделает это для вас за кулисами, но для этого нужен этот помощник "перенаправления". |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
### Создание *path operation* для его тестирования |
||||
|
|
||||
|
Теперь, чтобы проверить, что все работает, создайте *path operation*: |
||||
|
|
||||
|
{* ../../docs_src/custom_docs_ui/tutorial001.py hl[36:38] *} |
||||
|
|
||||
|
### Тестирование |
||||
|
|
||||
|
Теперь вы должны иметь возможность перейти на свою документацию по адресу <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 для документации |
||||
|
|
||||
|
Самостоятельный хостинг JavaScript и CSS может быть полезен, если, например, вам нужно, чтобы ваше приложение продолжало работать даже в автономном режиме, без доступа в интернет или в локальной сети. |
||||
|
|
||||
|
Здесь вы увидите, как самостоятельно размещать эти файлы в том же приложении FastAPI и настраивать документацию для их использования. |
||||
|
|
||||
|
### Структура файлов проекта |
||||
|
|
||||
|
Допустим, ваша структура файлов проекта выглядит следующим образом: |
||||
|
|
||||
|
``` |
||||
|
. |
||||
|
├── app |
||||
|
│ ├── __init__.py |
||||
|
│ ├── main.py |
||||
|
``` |
||||
|
|
||||
|
Теперь создайте каталог для хранения этих статических файлов. |
||||
|
|
||||
|
Ваша новая структура файлов может выглядеть так: |
||||
|
|
||||
|
``` |
||||
|
. |
||||
|
├── app |
||||
|
│ ├── __init__.py |
||||
|
│ ├── main.py |
||||
|
└── static/ |
||||
|
``` |
||||
|
|
||||
|
### Загрузка файлов |
||||
|
|
||||
|
Скачайте необходимые для документации статические файлы и поместите их в каталог `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 |
||||
|
``` |
||||
|
|
||||
|
### Обслуживание статических файлов |
||||
|
|
||||
|
* Импортируйте `StaticFiles`. |
||||
|
* "Подключите" экземпляр `StaticFiles()` в определенном пути. |
||||
|
|
||||
|
{* ../../docs_src/custom_docs_ui/tutorial002.py hl[7,11] *} |
||||
|
|
||||
|
### Тестирование статических файлов |
||||
|
|
||||
|
Запустите приложение и перейдите по адресу <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")): |
||||
|
... |
||||
|
``` |
||||
|
|
||||
|
Это подтверждает, что вы можете обслуживать статические файлы из вашего приложения и что вы разместили статические файлы для документации в правильном месте. |
||||
|
|
||||
|
Теперь мы можем настроить приложение для использования этих статических файлов в документации. |
||||
|
|
||||
|
### Отключение автоматической документации для статических файлов |
||||
|
|
||||
|
Как и при использовании пользовательского CDN, первым шагом является отключение автоматической документации, так как по умолчанию они используют CDN. |
||||
|
|
||||
|
Чтобы отключить их, установите их URL в `None` при создании вашего приложения `FastAPI`: |
||||
|
|
||||
|
{* ../../docs_src/custom_docs_ui/tutorial002.py hl[9] *} |
||||
|
|
||||
|
### Включение пользовательской документации для статических файлов |
||||
|
|
||||
|
И так же, как с пользовательским CDN, теперь вы можете создать *path operations* для пользовательской документации. |
||||
|
|
||||
|
Снова можно использовать внутренние функции 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 | Совет |
||||
|
|
||||
|
*Path operation* для `swagger_ui_redirect` является помощником, когда вы используете OAuth2. |
||||
|
|
||||
|
Если вы интегрируете свой API с провайдером OAuth2, вы сможете авторизоваться и вернуться к документации API с полученными учетными данными. И взаимодействовать с ним, используя реальную аутентификацию через OAuth2. |
||||
|
|
||||
|
Swagger UI сделает это для вас за кулисами, но для этого нужен этот помощник "перенаправления". |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
### Создание *path operation* для тестирования статических файлов |
||||
|
|
||||
|
Теперь, чтобы убедиться, что все работает, создайте *path operation*: |
||||
|
|
||||
|
{* ../../docs_src/custom_docs_ui/tutorial002.py hl[39:41] *} |
||||
|
|
||||
|
### Тестирование статических файлов 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 @@ |
|||||
|
# Классы кастомных запросов и APIRoute |
||||
|
|
||||
|
В некоторых случаях вы можете захотеть переопределить логику, используемую классами `Request` и `APIRoute`. |
||||
|
|
||||
|
В частности, это может быть хорошей альтернативой логике в middleware. |
||||
|
|
||||
|
Например, если вы хотите прочитать или изменить тело запроса до его обработки вашим приложением. |
||||
|
|
||||
|
/// danger | Опасность |
||||
|
|
||||
|
Это "продвинутая" функция. |
||||
|
|
||||
|
Если вы только начинаете с **FastAPI**, возможно, вам стоит пропустить этот раздел. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Примеры использования |
||||
|
|
||||
|
Некоторые примеры использования включают: |
||||
|
|
||||
|
* Конвертацию тел запросов, которые не являются JSON, в JSON (например, <a href="https://msgpack.org/index.html" class="external-link" target="_blank">`msgpack`</a>). |
||||
|
* Распаковку тел запросов, сжатых с помощью gzip. |
||||
|
* Автоматическое логирование всех тел запросов. |
||||
|
|
||||
|
## Обработка кастомных кодировок тел запросов |
||||
|
|
||||
|
Давайте рассмотрим, как использовать подкласс `Request` для распаковки запросов в формате gzip. |
||||
|
|
||||
|
И подкласс `APIRoute` для использования этого кастомного класса запроса. |
||||
|
|
||||
|
### Создание кастомного класса `GzipRequest` |
||||
|
|
||||
|
/// tip | Подсказка |
||||
|
|
||||
|
Это учебный пример, демонстрирующий, как это работает; если вам нужна поддержка Gzip, вы можете использовать предоставленный [`GzipMiddleware`](../advanced/middleware.md#gzipmiddleware){.internal-link target=_blank}. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
Сначала мы создаем класс `GzipRequest`, который будет переопределять метод `Request.body()` для распаковки тела при наличии соответствующего заголовка. |
||||
|
|
||||
|
Если в заголовке нет `gzip`, он не будет пытаться распаковать тело. |
||||
|
|
||||
|
Таким образом, один и тот же класс маршрута может обрабатывать как сжатые, так и несжатые запросы в формате gzip. |
||||
|
|
||||
|
{* ../../docs_src/custom_request_and_route/tutorial001.py hl[8:15] *} |
||||
|
|
||||
|
### Создание кастомного класса `GzipRoute` |
||||
|
|
||||
|
Далее, мы создаем кастомный подкласс `fastapi.routing.APIRoute`, который будет использовать `GzipRequest`. |
||||
|
|
||||
|
На этот раз он переопределит метод `APIRoute.get_route_handler()`. |
||||
|
|
||||
|
Этот метод возвращает функцию. И именно эта функция принимает запрос и возвращает ответ. |
||||
|
|
||||
|
Здесь мы используем его для создания `GzipRequest` из оригинального запроса. |
||||
|
|
||||
|
{* ../../docs_src/custom_request_and_route/tutorial001.py hl[18:26] *} |
||||
|
|
||||
|
/// note | Технические детали |
||||
|
|
||||
|
`Request` имеет атрибут `request.scope`, это просто Python `dict`, содержащий метаданные, связанные с запросом. |
||||
|
|
||||
|
`Request` также имеет `request.receive`, это функция для "получения" тела запроса. |
||||
|
|
||||
|
`scope` `dict` и функция `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**. |
||||
|
|
||||
|
## Доступ к телу запроса в обработчике исключений |
||||
|
|
||||
|
/// 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` в маршрутизаторе |
||||
|
|
||||
|
Вы также можете установить параметр `route_class` для `APIRouter`: |
||||
|
|
||||
|
{* ../../docs_src/custom_request_and_route/tutorial003.py hl[26] *} |
||||
|
|
||||
|
В этом примере операции пути под `router` будут использовать кастомный класс `TimedRoute` и добавят в ответ заголовок `X-Response-Time` с временем, затраченным на генерацию ответа: |
||||
|
|
||||
|
{* ../../docs_src/custom_request_and_route/tutorial003.py hl[13:20] *} |
@ -0,0 +1,80 @@ |
|||||
|
# Расширение OpenAPI |
||||
|
|
||||
|
Есть случаи, когда вам может понадобиться изменить сгенерированную схему OpenAPI. |
||||
|
|
||||
|
В этом разделе вы увидите, как это сделать. |
||||
|
|
||||
|
## Обычный процесс |
||||
|
|
||||
|
Обычный (по умолчанию) процесс таков: |
||||
|
|
||||
|
`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 и выше. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Переопределение стандартных значений |
||||
|
|
||||
|
Используя вышеуказанную информацию, вы можете использовать ту же вспомогательную функцию для генерации схемы 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** |
||||
|
|
||||
|
Сначала напишите все ваше приложение **FastAPI** как обычно: |
||||
|
|
||||
|
{* ../../docs_src/extending_openapi/tutorial001.py hl[1,4,7:9] *} |
||||
|
|
||||
|
### Генерация схемы OpenAPI |
||||
|
|
||||
|
Затем используйте ту же вспомогательную функцию для генерации схемы OpenAPI внутри функции `custom_openapi()`: |
||||
|
|
||||
|
{* ../../docs_src/extending_openapi/tutorial001.py hl[2,15:21] *} |
||||
|
|
||||
|
### Изменение схемы OpenAPI |
||||
|
|
||||
|
Теперь вы можете добавить расширение ReDoc, добавив настраиваемый `x-logo` в "объект" `info` схемы OpenAPI: |
||||
|
|
||||
|
{* ../../docs_src/extending_openapi/tutorial001.py hl[22:24] *} |
||||
|
|
||||
|
### Кэширование схемы OpenAPI |
||||
|
|
||||
|
Вы можете использовать свойство `.openapi_schema` в качестве "кэша" для хранения вашей сгенерированной схемы. |
||||
|
|
||||
|
Таким образом, вашему приложению не придется генерировать схему каждый раз, когда пользователь открывает вашу документацию API. |
||||
|
|
||||
|
Она будет сгенерирована только один раз, и затем будет использоваться та же закэшированная схема для следующих запросов. |
||||
|
|
||||
|
{* ../../docs_src/extending_openapi/tutorial001.py hl[13:14,25:26] *} |
||||
|
|
||||
|
### Переопределение метода |
||||
|
|
||||
|
Теперь вы можете заменить метод `.openapi()` на вашу новую функцию. |
||||
|
|
||||
|
{* ../../docs_src/extending_openapi/tutorial001.py hl[29] *} |
||||
|
|
||||
|
### Проверка |
||||
|
|
||||
|
Как только вы перейдете на <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 @@ |
|||||
|
# Общие - Как сделать - Рецепты |
||||
|
|
||||
|
Вот несколько указателей на другие места в документации для общих или частых вопросов. |
||||
|
|
||||
|
## Фильтрация данных - Безопасность |
||||
|
|
||||
|
Чтобы убедиться, что вы не возвращаете больше данных, чем следует, прочитайте документацию [Учебник - Модель ответа - Возвращаемый тип](../tutorial/response-model.md){.internal-link target=_blank}. |
||||
|
|
||||
|
## Теги документации - OpenAPI |
||||
|
|
||||
|
Чтобы добавить теги к вашим *операциям путей* и сгруппировать их в интерфейсе документации, прочитайте документацию [Учебник - Конфигурация операций пути - Теги](../tutorial/path-operation-configuration.md#tags){.internal-link target=_blank}. |
||||
|
|
||||
|
## Обзор и описание документации - OpenAPI |
||||
|
|
||||
|
Чтобы добавить обзор и описание к вашим *операциям путей* и показать их в интерфейсе документации, прочитайте документацию [Учебник - Конфигурация операций пути - Обзор и описание](../tutorial/path-operation-configuration.md#summary-and-description){.internal-link target=_blank}. |
||||
|
|
||||
|
## Описание ответа в документации - OpenAPI |
||||
|
|
||||
|
Чтобы определить описание ответа, показанное в интерфейсе документации, прочитайте документацию [Учебник - Конфигурация операций пути - Описание ответа](../tutorial/path-operation-configuration.md#response-description){.internal-link target=_blank}. |
||||
|
|
||||
|
## Устаревание документации *Операций пути* - OpenAPI |
||||
|
|
||||
|
Чтобы объявить устаревание *операции пути* и показать это в интерфейсе документации, прочитайте документацию [Учебник - Конфигурация операций пути - Устаревание](../tutorial/path-operation-configuration.md#deprecate-a-path-operation){.internal-link target=_blank}. |
||||
|
|
||||
|
## Конвертация любых данных в совместимые с JSON |
||||
|
|
||||
|
Чтобы конвертировать любые данные в совместимые с JSON, прочитайте документацию [Учебник - Кодировщик, совместимый с JSON](../tutorial/encoder.md){.internal-link target=_blank}. |
||||
|
|
||||
|
## OpenAPI Метаданные - Документация |
||||
|
|
||||
|
Чтобы добавить метаданные к вашей OpenAPI схеме, включая лицензию, версию, контактные данные и т.д., прочитайте документацию [Учебник - Метаданные и URL-адреса документации](../tutorial/metadata.md){.internal-link target=_blank}. |
||||
|
|
||||
|
## Настройка URL-адреса OpenAPI |
||||
|
|
||||
|
Чтобы настроить (или удалить) URL-адрес OpenAPI, прочитайте документацию [Учебник - Метаданные и URL-адреса документации](../tutorial/metadata.md#openapi-url){.internal-link target=_blank}. |
||||
|
|
||||
|
## URL-адреса документации OpenAPI |
||||
|
|
||||
|
Чтобы обновить URL-адреса, используемые для автоматически сгенерированных пользовательских интерфейсов документации, прочитайте документацию [Учебник - Метаданные и URL-адреса документации](../tutorial/metadata.md#docs-urls){.internal-link target=_blank}. |
@ -0,0 +1,60 @@ |
|||||
|
# GraphQL |
||||
|
|
||||
|
Так как **FastAPI** основан на стандарте **ASGI**, интегрировать любую библиотеку **GraphQL**, совместимую с ASGI, очень просто. |
||||
|
|
||||
|
Вы можете комбинировать обычные *маршруты* FastAPI с GraphQL в одном приложении. |
||||
|
|
||||
|
/// tip | Совет |
||||
|
|
||||
|
**GraphQL** решает некоторые очень специфичные случаи использования. |
||||
|
|
||||
|
Он имеет **преимущества** и **недостатки** по сравнению с обычными **веб API**. |
||||
|
|
||||
|
Убедитесь, что вы оценили, компенсируют ли **преимущества** для вашего случая использования **недостатки**. 🤓 |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Библиотеки GraphQL |
||||
|
|
||||
|
Вот некоторые из библиотек **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**, <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 |
||||
|
|
||||
|
Предыдущие версии 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>, так как он основан на аннотациях типов, а не на пользовательских классах и типах. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Узнать больше |
||||
|
|
||||
|
Вы можете узнать больше о **GraphQL** в <a href="https://graphql.org/" class="external-link" target="_blank">официальной документации GraphQL</a>. |
||||
|
|
||||
|
Вы также можете прочитать больше о каждой из упомянутых выше библиотек по предоставленным ссылкам. |
@ -0,0 +1,13 @@ |
|||||
|
# Как сделать - Рецепты |
||||
|
|
||||
|
Здесь вы найдете различные рецепты или руководства «как сделать» по **разным темам**. |
||||
|
|
||||
|
Большинство из этих идей будут более или менее **независимыми**, и в большинстве случаев вам нужно изучать их только в случае, если они применимы непосредственно к **вашему проекту**. |
||||
|
|
||||
|
Если что-то кажется интересным и полезным для вашего проекта, смело проверяйте это, но в противном случае вы, вероятно, сможете просто их пропустить. |
||||
|
|
||||
|
/// tip | Совет |
||||
|
|
||||
|
Если вы хотите **изучить FastAPI** структурированным образом (рекомендуется), идите и читайте [Учебник - Руководство пользователя](../tutorial/index.md){.internal-link target=_blank} глава за главой. |
||||
|
|
||||
|
/// |
@ -0,0 +1,104 @@ |
|||||
|
# Раздельные схемы OpenAPI для ввода и вывода или нет |
||||
|
|
||||
|
При использовании **Pydantic v2** созданный OpenAPI немного более точен и **правилен**, чем раньше. 😎 |
||||
|
|
||||
|
Фактически, в некоторых случаях, для одной и той же модели Pydantic в OpenAPI даже будет **две схемы JSON** — для ввода и вывода, в зависимости от того, имеют ли они **значения по умолчанию**. |
||||
|
|
||||
|
Посмотрим, как это работает, и как изменить это, если вам нужно это сделать. |
||||
|
|
||||
|
## Pydantic модели для ввода и вывода |
||||
|
|
||||
|
Предположим, у вас есть модель Pydantic со значениями по умолчанию, например, такая: |
||||
|
|
||||
|
{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:7] hl[7] *} |
||||
|
|
||||
|
### Модель для ввода |
||||
|
|
||||
|
Если вы используете эту модель в качестве входных данных, как здесь: |
||||
|
|
||||
|
{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:15] hl[14] *} |
||||
|
|
||||
|
...тогда поле `description` **не будет обязательным**. Потому что у него есть значение по умолчанию `None`. |
||||
|
|
||||
|
### Модель ввода в документации |
||||
|
|
||||
|
Вы можете подтвердить в документации, что поле `description` не имеет **красной звездочки**, оно не помечено как обязательное: |
||||
|
|
||||
|
<div class="screenshot"> |
||||
|
<img src="/img/tutorial/separate-openapi-schemas/image01.png"> |
||||
|
</div> |
||||
|
|
||||
|
### Модель для вывода |
||||
|
|
||||
|
Но если вы используете ту же модель для вывода, как здесь: |
||||
|
|
||||
|
{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py hl[19] *} |
||||
|
|
||||
|
...тогда, так как `description` имеет значение по умолчанию, если вы **ничего не возвращаете** для этого поля, оно все равно будет иметь это **значение по умолчанию**. |
||||
|
|
||||
|
### Модель для данных ответа вывода |
||||
|
|
||||
|
Если вы взаимодействуете с документацией и проверяете ответ, даже если код ничего не добавил в одно из полей `description`, JSON ответ содержит значение по умолчанию (`null`): |
||||
|
|
||||
|
<div class="screenshot"> |
||||
|
<img src="/img/tutorial/separate-openapi-schemas/image02.png"> |
||||
|
</div> |
||||
|
|
||||
|
Это означает, что у него **всегда будет значение**, просто иногда это значение может быть `None` (или `null` в терминах JSON). |
||||
|
|
||||
|
Это означает, что клиенты, использующие ваш API, не должны проверять, существует значение или нет, они могут **предполагать, что поле всегда будет там**, но в некоторых случаях у него будет значение по умолчанию `None`. |
||||
|
|
||||
|
Способ описания этого в OpenAPI состоит в том, чтобы пометить это поле как **обязательное**, поскольку оно всегда будет там. |
||||
|
|
||||
|
Из-за этого схема JSON для модели может отличаться в зависимости от того, используется ли она для **ввода или вывода**: |
||||
|
|
||||
|
* для **ввода** `description` **не будет обязательным** |
||||
|
* для **вывода** оно будет **обязательным** (и возможно `None`, или в терминах JSON `null`) |
||||
|
|
||||
|
### Модель для вывода в документации |
||||
|
|
||||
|
Вы тоже можете проверить модель вывода в документации, **оба** поля `name` и `description` помечены как **обязательные** с **красной звездочкой**: |
||||
|
|
||||
|
<div class="screenshot"> |
||||
|
<img src="/img/tutorial/separate-openapi-schemas/image03.png"> |
||||
|
</div> |
||||
|
|
||||
|
### Модель для ввода и вывода в документации |
||||
|
|
||||
|
И если вы проверите все доступные схемы (JSON схемы) в 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, они также будут более точными, с лучшим **опытом разработчика** и согласованностью. 🎉 |
||||
|
|
||||
|
## Не разделяйте схемы |
||||
|
|
||||
|
Теперь, в некоторых случаях вы, возможно, захотите иметь **одну и ту же схему для ввода и вывода**. |
||||
|
|
||||
|
Возможно, главным случаем использования является то, что если у вас уже есть сгенерированный клиентский код/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] *} |
||||
|
|
||||
|
### Одна и та же схема для моделей ввода и вывода в документации |
||||
|
|
||||
|
И теперь будет одна единая схема для ввода и вывода для модели, только `Item`, и в ней `description` будет **необязательным**: |
||||
|
|
||||
|
<div class="screenshot"> |
||||
|
<img src="/img/tutorial/separate-openapi-schemas/image05.png"> |
||||
|
</div> |
||||
|
|
||||
|
Это то же самое поведение, что и в Pydantic v1. 🤓 |
@ -0,0 +1,7 @@ |
|||||
|
# Тестирование базы данных |
||||
|
|
||||
|
Вы можете изучить базы данных, 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 @@ |
|||||
|
# Ресурсы |
||||
|
|
||||
|
Дополнительные ресурсы, внешние ссылки, статьи и многое другое. ✈️ |
Loading…
Reference in new issue