mirror of
https://github.com/fastapi/fastapi.git
synced 2026-04-16 04:53:11 -04:00
🌐 Update Russian translations for existing pages (LLM-generated) (#14123)
* Update Russian translations for modified pages * docs: fix translation for multiprocessing * Update Russian translations for other existing pages * Apply changes from latest PRs: 13917 and 14099 --------- Co-authored-by: vldmrdev <70532790+vldmrdev@users.noreply.github.com>
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
# О проекте
|
||||
# О проекте { #about }
|
||||
|
||||
FastAPI: внутреннее устройство, повлиявшие технологии и всё такое прочее. 🤓
|
||||
О FastAPI, его дизайне, источниках вдохновения и многом другом. 🤓
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
# Дополнительные статус коды
|
||||
# Дополнительные статус-коды { #additional-status-codes }
|
||||
|
||||
По умолчанию **FastAPI** возвращает ответы, используя `JSONResponse`, помещая содержимое, которое вы возвращаете из вашей *операции пути*, внутрь этого `JSONResponse`.
|
||||
По умолчанию **FastAPI** будет возвращать ответы, используя `JSONResponse`, помещая содержимое, которое вы возвращаете из вашей *операции пути*, внутрь этого `JSONResponse`.
|
||||
|
||||
Он будет использовать код статуса по умолчанию или тот, который вы укажете в вашей *операции пути*.
|
||||
Он будет использовать статус-код по умолчанию или тот, который вы укажете в вашей *операции пути*.
|
||||
|
||||
## Дополнительные статус коды
|
||||
## Дополнительные статус-коды { #additional-status-codes_1 }
|
||||
|
||||
Если вы хотите возвращать дополнительный статус код помимо основного, вы можете сделать это, возвращая объект `Response` напрямую, как `JSONResponse`, и устанавливая нужный статус код напрямую.
|
||||
Если вы хотите возвращать дополнительные статус-коды помимо основного, вы можете сделать это, возвращая `Response` напрямую, например `JSONResponse`, и устанавливая дополнительный статус-код напрямую.
|
||||
|
||||
Например, скажем, вы хотите создать *операцию пути*, которая позволяет обновлять элементы и возвращает HTTP-код 200 "OK" при успешном выполнении.
|
||||
Например, предположим, что вы хотите иметь *операцию пути*, которая позволяет обновлять элементы и возвращает HTTP статус-код 200 «OK» при успешном выполнении.
|
||||
|
||||
Но вы также хотите, чтобы она принимала новые элементы. И если элемент ранее не существовал, он создаётся, и возвращался HTTP-код 201 "Created".
|
||||
Но вы также хотите, чтобы она принимала новые элементы. И если элементы ранее не существовали, она создаёт их и возвращает HTTP статус-код 201 «Created».
|
||||
|
||||
Чтобы реализовать это, импортируйте `JSONResponse` и возвращайте ваш контент напрямую, устанавливая нужный `status_code`:
|
||||
Чтобы добиться этого, импортируйте `JSONResponse` и верните туда свой контент напрямую, установив нужный вам `status_code`:
|
||||
|
||||
{* ../../docs_src/additional_status_codes/tutorial001_an_py310.py hl[4,25] *}
|
||||
|
||||
/// warning | Внимание
|
||||
|
||||
Когда вы возвращаете объект `Response` напрямую, как в примере выше, он будет возвращён как есть.
|
||||
Когда вы возвращаете `Response` напрямую, как в примере выше, он будет возвращён как есть.
|
||||
|
||||
Он не будет сериализован при помощи модели и т.д.
|
||||
Он не будет сериализован с помощью модели и т.п.
|
||||
|
||||
Убедитесь, что в нём содержатся именно те данные, которые вы хотите, и что значения являются валидным JSON (если вы используете `JSONResponse`).
|
||||
Убедитесь, что в нём именно те данные, которые вы хотите, и что значения являются валидным JSON (если вы используете `JSONResponse`).
|
||||
|
||||
///
|
||||
|
||||
@@ -30,12 +30,12 @@
|
||||
|
||||
Вы также можете использовать `from starlette.responses import JSONResponse`.
|
||||
|
||||
**FastAPI** предоставляет тот же `starlette.responses` через `fastapi.responses` просто для вашего удобства, как разработчика. Но большинство доступных Response-классов поступают напрямую из Starlette. То же самое касается и `status`.
|
||||
**FastAPI** предоставляет тот же `starlette.responses` через `fastapi.responses` просто для вашего удобства как разработчика. Но большинство доступных Response-классов приходят напрямую из Starlette. То же самое со `status`.
|
||||
|
||||
///
|
||||
|
||||
## OpenAPI и документация API
|
||||
## OpenAPI и документация API { #openapi-and-api-docs }
|
||||
|
||||
Если вы возвращаете дополнительные коды статусов и ответы напрямую, они не будут включены в схему OpenAPI (документацию API), потому что FastAPI не может заранее знать, что вы собираетесь вернуть.
|
||||
Если вы возвращаете дополнительные статус-коды и ответы напрямую, они не будут включены в схему OpenAPI (документацию API), потому что у FastAPI нет способа заранее знать, что вы собираетесь вернуть.
|
||||
|
||||
Но вы можете задокументировать это в вашем коде, используя: [Дополнительные ответы в OpenAPI](additional-responses.md){.internal-link target=_blank}.
|
||||
Но вы можете задокументировать это в своём коде, используя: [Дополнительные ответы](additional-responses.md){.internal-link target=_blank}.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Асинхронное тестирование
|
||||
# Асинхронное тестирование { #async-tests }
|
||||
|
||||
Вы уже видели как тестировать **FastAPI** приложение, используя имеющийся класс `TestClient`. К этому моменту вы видели только как писать тесты в синхронном стиле без использования `async` функций.
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
|
||||
Давайте рассмотрим, как мы можем это реализовать.
|
||||
|
||||
## pytest.mark.anyio
|
||||
## pytest.mark.anyio { #pytest-mark-anyio }
|
||||
|
||||
Если мы хотим вызывать асинхронные функции в наших тестах, то наши тестовые функции должны быть асинхронными. AnyIO предоставляет для этого отличный плагин, который позволяет нам указывать, какие тестовые функции должны вызываться асинхронно.
|
||||
|
||||
## HTTPX
|
||||
## HTTPX { #httpx }
|
||||
|
||||
Даже если **FastAPI** приложение использует обычные функции `def` вместо `async def`, это все равно `async` приложение 'под капотом'.
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
`TestClient` основан на <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX</a>, и, к счастью, мы можем использовать его (`HTTPX`) напрямую для тестирования API.
|
||||
|
||||
## Пример
|
||||
## Пример { #example }
|
||||
|
||||
В качестве простого примера, давайте рассмотрим файловую структуру, схожую с описанной в [Большие приложения](../tutorial/bigger-applications.md){.internal-link target=_blank} и [Тестирование](../tutorial/testing.md){.internal-link target=_blank}:
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
|
||||
{* ../../docs_src/async_tests/test_main.py *}
|
||||
|
||||
## Запуск тестов
|
||||
## Запуск тестов { #run-it }
|
||||
|
||||
Вы можете запустить свои тесты как обычно:
|
||||
|
||||
@@ -52,7 +52,7 @@ $ pytest
|
||||
|
||||
</div>
|
||||
|
||||
## Подробнее
|
||||
## Подробнее { #in-detail }
|
||||
|
||||
Маркер `@pytest.mark.anyio` говорит pytest, что тестовая функция должна быть вызвана асинхронно:
|
||||
|
||||
@@ -88,7 +88,7 @@ response = client.get('/')
|
||||
|
||||
///
|
||||
|
||||
## Вызов других асинхронных функций
|
||||
## Вызов других асинхронных функций { #other-asynchronous-function-calls }
|
||||
|
||||
Теперь тестовая функция стала асинхронной, поэтому внутри нее вы можете вызывать также и другие `async` функции, не связанные с отправлением запросов в ваше FastAPI приложение. Как если бы вы вызывали их в любом другом месте вашего кода.
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# Расширенное руководство пользователя
|
||||
# Расширенное руководство пользователя { #advanced-user-guide }
|
||||
|
||||
## Дополнительные возможности
|
||||
## Дополнительные возможности { #additional-features }
|
||||
|
||||
Основное [Учебник - Руководство пользователя](../tutorial/index.md){.internal-link target=_blank} должно быть достаточно, чтобы познакомить вас со всеми основными функциями **FastAPI**.
|
||||
|
||||
В следующих разделах вы увидите другие варианты, конфигурации и дополнительные возможности.
|
||||
|
||||
/// tip
|
||||
/// tip | Совет
|
||||
|
||||
Следующие разделы **не обязательно являются "продвинутыми"**.
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
///
|
||||
|
||||
## Сначала прочитайте Учебник - Руководство пользователя
|
||||
## Сначала прочитайте Учебник - Руководство пользователя { #read-the-tutorial-first }
|
||||
|
||||
Вы все еще можете использовать большинство функций **FastAPI** со знаниями из [Учебник - Руководство пользователя](../tutorial/index.md){.internal-link target=_blank}.
|
||||
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
# Response - Изменение cтатус кода
|
||||
# Response - Изменение статус-кода { #response-change-status-code }
|
||||
|
||||
Вы, вероятно, уже читали о том, что можно установить [Состояние ответа по умолчанию](../tutorial/response-status-code.md){.internal-link target=_blank}.
|
||||
Вы, вероятно, уже читали о том, что можно установить [статус-код ответа по умолчанию](../tutorial/response-status-code.md){.internal-link target=_blank}.
|
||||
|
||||
Но в некоторых случаях вам нужно вернуть код состояния, отличный от установленного по умолчанию.
|
||||
Но в некоторых случаях нужно вернуть другой статус-код, отличный от значения по умолчанию.
|
||||
|
||||
## Пример использования
|
||||
## Пример использования { #use-case }
|
||||
|
||||
Например, представьте, что вы хотите возвращать HTTP код состояния "OK" `200` по умолчанию.
|
||||
Например, представьте, что вы хотите по умолчанию возвращать HTTP статус-код «OK» `200`.
|
||||
|
||||
Но если данные не существовали, вы хотите создать их и вернуть HTTP код состояния "CREATED" `201`.
|
||||
Но если данные не существовали, вы хотите создать их и вернуть HTTP статус-код «CREATED» `201`.
|
||||
|
||||
При этом вы всё ещё хотите иметь возможность фильтровать и преобразовывать возвращаемые данные с помощью `response_model`.
|
||||
|
||||
Для таких случаев вы можете использовать параметр `Response`.
|
||||
|
||||
## Использование параметра `Response`
|
||||
## Использование параметра `Response` { #use-a-response-parameter }
|
||||
|
||||
Вы можете объявить параметр типа `Response` в вашей *функции обработки пути* (так же как для cookies и headers).
|
||||
Вы можете объявить параметр типа `Response` в вашей *функции обработки пути* (как и для cookies и HTTP-заголовков).
|
||||
|
||||
И затем вы можете установить `status_code` в этом *временном* объекте ответа.
|
||||
|
||||
@@ -26,6 +26,6 @@
|
||||
|
||||
И если вы объявили `response_model`, он всё равно будет использоваться для фильтрации и преобразования возвращаемого объекта.
|
||||
|
||||
**FastAPI** будет использовать этот *временный* ответ для извлечения кода состояния (а также cookies и headers) и поместит их в финальный ответ, который содержит возвращаемое вами значение, отфильтрованное любым `response_model`.
|
||||
**FastAPI** будет использовать этот *временный* ответ для извлечения статус-кода (а также cookies и HTTP-заголовков) и поместит их в финальный ответ, который содержит возвращаемое вами значение, отфильтрованное любым `response_model`.
|
||||
|
||||
Вы также можете объявить параметр `Response` в зависимостях и установить код состояния в них. Но помните, что последнее установленное значение будет иметь приоритет.
|
||||
Вы также можете объявить параметр `Response` в зависимостях и установить в них статус-код. Но помните, что последнее установленное значение будет иметь приоритет.
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
# Cookies в ответе { #response-cookies }
|
||||
|
||||
# Cookies в ответе
|
||||
## Использование параметра `Response` { #use-a-response-parameter }
|
||||
|
||||
## Использование параметра `Response`
|
||||
|
||||
Вы можете объявить параметр типа `Response` в вашей функции эндпоинта.
|
||||
Вы можете объявить параметр типа `Response` в вашей функции-обработчике пути.
|
||||
|
||||
Затем установить cookies в этом временном объекте ответа.
|
||||
|
||||
@@ -13,36 +12,40 @@
|
||||
|
||||
Если вы указали `response_model`, он всё равно будет использоваться для фильтрации и преобразования возвращаемого объекта.
|
||||
|
||||
**FastAPI** извлечет cookies (а также заголовки и коды состояния) из временного ответа и включит их в окончательный ответ, содержащий ваше возвращаемое значение, отфильтрованное через `response_model`.
|
||||
**FastAPI** извлечет cookies (а также HTTP-заголовки и статус-код) из временного ответа и включит их в окончательный ответ, содержащий ваше возвращаемое значение, отфильтрованное через `response_model`.
|
||||
|
||||
Вы также можете объявить параметр типа Response в зависимостях и устанавливать cookies (и заголовки) там.
|
||||
Вы также можете объявить параметр типа `Response` в зависимостях и устанавливать cookies (и HTTP-заголовки) там.
|
||||
|
||||
## Возвращение `Response` напрямую
|
||||
## Возвращение `Response` напрямую { #return-a-response-directly }
|
||||
|
||||
Вы также можете установить cookies, если возвращаете `Response` напрямую в вашем коде.
|
||||
Вы также можете установить Cookies, если возвращаете `Response` напрямую в вашем коде.
|
||||
|
||||
Для этого создайте объект `Response`, как описано в разделе [Возвращение ответа напрямую](response-directly.md){.target=_blank}.
|
||||
Для этого создайте объект `Response`, как описано в разделе [Возвращение ответа напрямую](response-directly.md){.internal-link target=_blank}.
|
||||
|
||||
Затем установите cookies и верните этот объект:
|
||||
|
||||
{* ../../docs_src/response_cookies/tutorial001.py hl[10:12] *}
|
||||
|
||||
/// tip | Примечание
|
||||
Имейте в виду, что если вы возвращаете ответ напрямую, вместо использования параметра `Response`, **FastAPI** отправит его без дополнительной обработки.
|
||||
/// tip | Совет
|
||||
|
||||
Убедитесь, что ваши данные имеют корректный тип. Например, они должны быть совместимы с JSON, если вы используете `JSONResponse`.
|
||||
Имейте в виду, что если вы возвращаете ответ напрямую, вместо использования параметра `Response`, FastAPI вернёт его напрямую.
|
||||
|
||||
Убедитесь, что ваши данные имеют корректный тип. Например, они должны быть совместимы с JSON, если вы возвращаете `JSONResponse`.
|
||||
|
||||
Также убедитесь, что вы не отправляете данные, которые должны были быть отфильтрованы через `response_model`.
|
||||
|
||||
///
|
||||
|
||||
### Дополнительная информация
|
||||
### Дополнительная информация { #more-info }
|
||||
|
||||
/// note | Технические детали
|
||||
|
||||
Вы также можете использовать `from starlette.responses import Response` или `from starlette.responses import JSONResponse`.
|
||||
|
||||
**FastAPI** предоставляет `fastapi.responses`, которые являются теми же объектами, что и `starlette.responses`, просто для удобства. Однако большинство доступных типов ответов поступает непосредственно из **Starlette**.
|
||||
|
||||
Для установки заголовков и cookies `Response` используется часто, поэтому **FastAPI** также предоставляет его через `fastapi.responses`.
|
||||
И так как `Response` часто используется для установки HTTP-заголовков и cookies, **FastAPI** также предоставляет его как `fastapi.Response`.
|
||||
|
||||
///
|
||||
|
||||
Чтобы увидеть все доступные параметры и настройки, ознакомьтесь с <a href="https://www.starlette.io/responses/#set-cookie" class="external-link" target="_blank">документацией Starlette</a>.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Возврат ответа напрямую
|
||||
# Возврат ответа напрямую { #return-a-response-directly }
|
||||
|
||||
Когда вы создаёте **FastAPI** *операцию пути*, вы можете возвращать из неё любые данные: `dict`, `list`, Pydantic-модель, модель базы данных и т.д.
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
|
||||
Но вы можете возвращать `JSONResponse` напрямую из ваших *операций пути*.
|
||||
|
||||
Это может быть полезно, например, если нужно вернуть пользовательские заголовки или куки.
|
||||
Это может быть полезно, например, если нужно вернуть пользовательские HTTP-заголовки или cookie.
|
||||
|
||||
## Возврат `Response`
|
||||
## Возврат `Response` { #return-a-response }
|
||||
|
||||
На самом деле, вы можете возвращать любой объект `Response` или его подкласс.
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
Это даёт вам большую гибкость. Вы можете возвращать любые типы данных, переопределять любые объявления или валидацию данных и т.д.
|
||||
|
||||
## Использование `jsonable_encoder` в `Response`
|
||||
## Использование `jsonable_encoder` в `Response` { #using-the-jsonable-encoder-in-a-response }
|
||||
|
||||
Поскольку **FastAPI** не изменяет объект `Response`, который вы возвращаете, вы должны убедиться, что его содержимое готово к отправке.
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
|
||||
///
|
||||
|
||||
## Возврат пользовательского `Response`
|
||||
## Возврат пользовательского `Response` { #returning-a-custom-response }
|
||||
|
||||
Пример выше показывает все необходимые части, но он пока не очень полезен, так как вы могли бы просто вернуть `item` напрямую, и **FastAPI** поместил бы его в `JSONResponse`, преобразовав в `dict` и т.д. Всё это происходит по умолчанию.
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
|
||||
{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *}
|
||||
|
||||
## Примечания
|
||||
## Примечания { #notes }
|
||||
|
||||
Когда вы возвращаете объект `Response` напрямую, его данные не валидируются, не преобразуются (не сериализуются) и не документируются автоматически.
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# Веб-сокеты
|
||||
# Веб-сокеты { #websockets }
|
||||
|
||||
Вы можете использовать <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API" class="external-link" target="_blank">веб-сокеты</a> в **FastAPI**.
|
||||
|
||||
## Установка `WebSockets`
|
||||
## Установка `websockets` { #install-websockets }
|
||||
|
||||
Убедитесь, что [виртуальная среда](../virtual-environments.md){.internal-link target=_blank} создана, активируйте её и установите `websockets`:
|
||||
Убедитесь, что вы создали [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активировали его и установили `websockets` (библиотека Python, упрощающая работу с протоколом "WebSocket"):
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -16,31 +16,31 @@ $ pip install websockets
|
||||
|
||||
</div>
|
||||
|
||||
## Клиент WebSockets
|
||||
## Клиент WebSockets { #websockets-client }
|
||||
|
||||
### Рабочее приложение
|
||||
### В продакшн { #in-production }
|
||||
|
||||
Скорее всего, в вашей реальной продуктовой системе есть фронтенд, реализованный при помощи современных фреймворков React, Vue.js или Angular.
|
||||
В продакшн у вас, вероятно, есть фронтенд, созданный с помощью современного фреймворка вроде React, Vue.js или Angular.
|
||||
|
||||
И наверняка для взаимодействия с бекендом через веб-сокеты вы будете использовать средства фронтенда.
|
||||
И для взаимодействия с бекендом по WebSocket вы, скорее всего, будете использовать инструменты вашего фронтенда.
|
||||
|
||||
Также у вас может быть нативное мобильное приложение, коммуницирующее непосредственно с веб-сокетами на бекенд-сервере.
|
||||
Также у вас может быть нативное мобильное приложение, которое напрямую, нативным кодом, взаимодействует с вашим WebSocket-бекендом.
|
||||
|
||||
Либо вы можете сделать какой-либо другой способ взаимодействия с веб-сокетами.
|
||||
Либо у вас может быть любой другой способ взаимодействия с WebSocket-эндпоинтом.
|
||||
|
||||
---
|
||||
|
||||
Но для этого примера мы воспользуемся очень простым HTML документом с небольшими вставками JavaScript кода.
|
||||
Но для этого примера мы воспользуемся очень простым HTML‑документом с небольшим JavaScript, всё внутри одной длинной строки.
|
||||
|
||||
Конечно же это неоптимально, и на практике так делать не стоит.
|
||||
Конечно же, это неоптимально, и вы бы не использовали это в продакшн.
|
||||
|
||||
В реальных приложениях стоит воспользоваться одним из вышеупомянутых способов.
|
||||
В продакшн у вас был бы один из вариантов выше.
|
||||
|
||||
Для примера нам нужен наиболее простой способ, который позволит сосредоточиться на серверной части веб-сокетов и получить рабочий код:
|
||||
Для примера нам нужен наиболее простой способ, который позволит сосредоточиться на серверной части веб‑сокетов и получить рабочий код:
|
||||
|
||||
{* ../../docs_src/websockets/tutorial001.py hl[2,6:38,41:43] *}
|
||||
|
||||
## Создание `websocket`
|
||||
## Создание `websocket` { #create-a-websocket }
|
||||
|
||||
Создайте `websocket` в своем **FastAPI** приложении:
|
||||
|
||||
@@ -54,7 +54,7 @@ $ pip install websockets
|
||||
|
||||
///
|
||||
|
||||
## Ожидание и отправка сообщений
|
||||
## Ожидание и отправка сообщений { #await-for-messages-and-send-messages }
|
||||
|
||||
Через эндпоинт веб-сокета вы можете получать и отправлять сообщения.
|
||||
|
||||
@@ -62,7 +62,7 @@ $ pip install websockets
|
||||
|
||||
Вы можете получать и отправлять двоичные, текстовые и JSON данные.
|
||||
|
||||
## Проверка в действии
|
||||
## Проверка в действии { #try-it }
|
||||
|
||||
Если ваш файл называется `main.py`, то запустите приложение командой:
|
||||
|
||||
@@ -96,7 +96,7 @@ $ fastapi dev main.py
|
||||
|
||||
И все они будут использовать одно и то же веб-сокет соединение.
|
||||
|
||||
## Использование `Depends` и не только
|
||||
## Использование `Depends` и не только { #using-depends-and-others }
|
||||
|
||||
Вы можете импортировать из `fastapi` и использовать в эндпоинте вебсокета:
|
||||
|
||||
@@ -119,7 +119,7 @@ $ fastapi dev main.py
|
||||
|
||||
///
|
||||
|
||||
### Веб-сокеты с зависимостями: проверка в действии
|
||||
### Веб-сокеты с зависимостями: проверка в действии { #try-the-websockets-with-dependencies }
|
||||
|
||||
Если ваш файл называется `main.py`, то запустите приложение командой:
|
||||
|
||||
@@ -150,7 +150,7 @@ $ fastapi dev main.py
|
||||
|
||||
<img src="/img/tutorial/websockets/image05.png">
|
||||
|
||||
## Обработка отключений и работа с несколькими клиентами
|
||||
## Обработка отключений и работа с несколькими клиентами { #handling-disconnections-and-multiple-clients }
|
||||
|
||||
Если веб-сокет соединение закрыто, то `await websocket.receive_text()` вызовет исключение `WebSocketDisconnect`, которое можно поймать и обработать как в этом примере:
|
||||
|
||||
@@ -168,7 +168,7 @@ $ fastapi dev main.py
|
||||
Client #1596980209979 left the chat
|
||||
```
|
||||
|
||||
/// tip | Примечание
|
||||
/// tip | Подсказка
|
||||
|
||||
Приложение выше - это всего лишь простой минимальный пример, демонстрирующий обработку и передачу сообщений нескольким веб-сокет соединениям.
|
||||
|
||||
@@ -178,7 +178,7 @@ Client #1596980209979 left the chat
|
||||
|
||||
///
|
||||
|
||||
## Дополнительная информация
|
||||
## Дополнительная информация { #more-info }
|
||||
|
||||
Для более глубокого изучения темы воспользуйтесь документацией Starlette:
|
||||
|
||||
|
||||
@@ -1,104 +1,94 @@
|
||||
# Альтернативы, источники вдохновения и сравнения
|
||||
# Альтернативы, источники вдохновения и сравнения { #alternatives-inspiration-and-comparisons }
|
||||
|
||||
Что вдохновило на создание **FastAPI**, сравнение его с альтернативами и чему он научился у них.
|
||||
Что вдохновило **FastAPI**, сравнение с альтернативами и чему он у них научился.
|
||||
|
||||
## Введение
|
||||
## Введение { #intro }
|
||||
|
||||
**FastAPI** не существовал бы, если б не было более ранних работ других людей.
|
||||
**FastAPI** не существовал бы без предыдущих работ других людей.
|
||||
|
||||
Они создали большое количество инструментов, которые вдохновили меня на создание **FastAPI**.
|
||||
Было создано множество инструментов, которые вдохновили на его появление.
|
||||
|
||||
Я всячески избегал создания нового фреймворка в течение нескольких лет.
|
||||
Сначала я пытался собрать все нужные функции, которые ныне есть в **FastAPI**, используя множество различных фреймворков, плагинов и инструментов.
|
||||
Я несколько лет избегал создания нового фреймворка. Сначала пытался закрыть все возможности, которые сейчас предоставляет **FastAPI**, с помощью множества разных фреймворков, плагинов и инструментов.
|
||||
|
||||
Но в какой-то момент не осталось другого выбора, кроме как создать что-то, что предоставляло бы все эти функции сразу.
|
||||
Взять самые лучшие идеи из предыдущих инструментов и, используя новые возможности Python (которых не было до версии 3.6, то есть подсказки типов), объединить их.
|
||||
Но в какой-то момент не осталось другого варианта, кроме как создать что-то, что включает все эти возможности, взяв лучшие идеи из прежних инструментов и совместив их максимально удачным образом, используя возможности языка, которых прежде не было (аннотации типов в Python 3.6+).
|
||||
|
||||
## Предшествующие инструменты
|
||||
## Предшествующие инструменты { #previous-tools }
|
||||
|
||||
### <a href="https://www.djangoproject.com/" class="external-link" target="_blank">Django</a>
|
||||
### <a href="https://www.djangoproject.com/" class="external-link" target="_blank">Django</a> { #django }
|
||||
|
||||
Это самый популярный Python-фреймворк, и он пользуется доверием.
|
||||
Он используется для создания проектов типа Instagram.
|
||||
Это самый популярный Python-фреймворк, ему широко доверяют. Он используется для построения систем вроде Instagram.
|
||||
|
||||
Django довольно тесно связан с реляционными базами данных (такими как MySQL или PostgreSQL), потому использовать NoSQL базы данных (например, Couchbase, MongoDB, Cassandra и т.п.) в качестве основного хранилища данных - непросто.
|
||||
Он относительно тесно связан с реляционными базами данных (например, MySQL или PostgreSQL), поэтому использовать NoSQL-базу данных (например, Couchbase, MongoDB, Cassandra и т. п.) в качестве основного хранилища не очень просто.
|
||||
|
||||
Он был создан для генерации HTML-страниц на сервере, а не для создания API, используемых современными веб-интерфейсами (React, Vue.js, Angular и т.п.) или другими системами (например, <abbr title="Интернет вещей">IoT</abbr>) взаимодействующими с сервером.
|
||||
Он был создан для генерации HTML на бэкенде, а не для создания API, используемых современным фронтендом (например, React, Vue.js и Angular) или другими системами (например, устройствами <abbr title="Internet of Things – Интернет вещей">IoT</abbr>), которые с ним общаются.
|
||||
|
||||
### <a href="https://www.django-rest-framework.org/" class="external-link" target="_blank">Django REST Framework</a>
|
||||
### <a href="https://www.django-rest-framework.org/" class="external-link" target="_blank">Django REST Framework</a> { #django-rest-framework }
|
||||
|
||||
Фреймворк Django REST был создан, как гибкий инструментарий для создания веб-API на основе Django.
|
||||
Django REST Framework был создан как гибкий набор инструментов для построения веб-API поверх Django, чтобы улучшить его возможности в части API.
|
||||
|
||||
DRF использовался многими компаниями, включая Mozilla, Red Hat и Eventbrite.
|
||||
Он используется многими компаниями, включая Mozilla, Red Hat и Eventbrite.
|
||||
|
||||
Это был один из первых примеров **автоматического документирования API** и это была одна из первых идей, вдохновивших на создание **FastAPI**.
|
||||
Это был один из первых примеров **автоматической документации API**, и именно эта идея одной из первых вдохновила на «поиск» **FastAPI**.
|
||||
|
||||
/// note | Заметка
|
||||
|
||||
Django REST Framework был создан Tom Christie.
|
||||
Он же создал Starlette и Uvicorn, на которых основан **FastAPI**.
|
||||
Django REST Framework был создан Томом Кристи. Он же создал Starlette и Uvicorn, на которых основан **FastAPI**.
|
||||
|
||||
///
|
||||
|
||||
/// check | Идея для **FastAPI**
|
||||
/// check | Вдохновило **FastAPI** на
|
||||
|
||||
Должно быть автоматическое создание документации API с пользовательским веб-интерфейсом.
|
||||
Наличие пользовательского веб-интерфейса с автоматической документацией API.
|
||||
|
||||
///
|
||||
|
||||
### <a href="https://flask.palletsprojects.com" class="external-link" target="_blank">Flask</a>
|
||||
### <a href="https://flask.palletsprojects.com" class="external-link" target="_blank">Flask</a> { #flask }
|
||||
|
||||
Flask - это "микрофреймворк", в нём нет интеграции с базами данных и многих других вещей, которые предустановлены в Django.
|
||||
Flask — это «микрофреймворк», он не включает интеграции с базами данных и многие другие вещи, которые в Django идут «из коробки».
|
||||
|
||||
Его простота и гибкость дают широкие возможности, такие как использование баз данных NoSQL в качестве основной системы хранения данных.
|
||||
Эта простота и гибкость позволяет, например, использовать NoSQL-базы в качестве основной системы хранения данных.
|
||||
|
||||
Он очень прост, его изучение интуитивно понятно, хотя в некоторых местах документация довольно техническая.
|
||||
Он очень прост, его относительно легко интуитивно освоить, хотя местами документация довольно техническая.
|
||||
|
||||
Flask часто используется и для приложений, которым не нужна база данных, настройки прав доступа для пользователей и прочие из множества функций, предварительно встроенных в Django.
|
||||
Хотя многие из этих функций могут быть добавлены с помощью плагинов.
|
||||
Его также часто используют для приложений, которым не нужна база данных, управление пользователями или многие другие функции, предварительно встроенные в Django. Хотя многие из этих возможностей можно добавить плагинами.
|
||||
|
||||
Такое разделение на части и то, что это "микрофреймворк", который можно расширить, добавляя необходимые возможности, было ключевой особенностью, которую я хотел сохранить.
|
||||
Такое разбиение на части и то, что это «микрофреймворк», который можно расширять ровно под нужды, — ключевая особенность, которую хотелось сохранить.
|
||||
|
||||
Простота Flask, показалась мне подходящей для создания API.
|
||||
Но ещё нужно было найти "Django REST Framework" для Flask.
|
||||
С учётом простоты Flask он казался хорошим вариантом для создания API. Следующим было найти «Django REST Framework» для Flask.
|
||||
|
||||
/// check | Идеи для **FastAPI**
|
||||
/// check | Вдохновило **FastAPI** на
|
||||
|
||||
Это будет микрофреймворк. К нему легко будет добавить необходимые инструменты и части.
|
||||
Быть микро-фреймворком. Облегчить комбинирование необходимых инструментов и компонентов.
|
||||
|
||||
Должна быть простая и лёгкая в использовании система маршрутизации запросов.
|
||||
Иметь простую и удобную систему маршрутизации.
|
||||
|
||||
///
|
||||
|
||||
### <a href="https://requests.readthedocs.io" class="external-link" target="_blank">Requests</a>
|
||||
### <a href="https://requests.readthedocs.io" class="external-link" target="_blank">Requests</a> { #requests }
|
||||
|
||||
На самом деле **FastAPI** не является альтернативой **Requests**.
|
||||
Их область применения очень разная.
|
||||
**FastAPI** на самом деле не альтернатива **Requests**. Их области применения очень различны.
|
||||
|
||||
В принципе, можно использовать Requests *внутри* приложения FastAPI.
|
||||
Обычно Requests используют даже внутри приложения FastAPI.
|
||||
|
||||
Но всё же я использовал в FastAPI некоторые идеи из Requests.
|
||||
И всё же **FastAPI** во многом вдохновлялся Requests.
|
||||
|
||||
**Requests** - это библиотека для взаимодействия с API в качестве клиента,
|
||||
в то время как **FastAPI** - это библиотека для *создания* API (то есть сервера).
|
||||
**Requests** — это библиотека для взаимодействия с API (как клиент), а **FastAPI** — библиотека для создания API (как сервер).
|
||||
|
||||
Они, так или иначе, диаметрально противоположны и дополняют друг друга.
|
||||
Они, в каком-то смысле, находятся на противоположных концах и дополняют друг друга.
|
||||
|
||||
Requests имеет очень простой и понятный дизайн, очень прост в использовании и имеет разумные значения по умолчанию.
|
||||
И в то же время он очень мощный и настраиваемый.
|
||||
Requests имеет очень простой и понятный дизайн, им очень легко пользоваться, есть разумные значения по умолчанию. И при этом он очень мощный и настраиваемый.
|
||||
|
||||
Вот почему на официальном сайте написано:
|
||||
Именно поэтому на официальном сайте сказано:
|
||||
|
||||
> Requests - один из самых загружаемых пакетов Python всех времен
|
||||
> Requests — один из самых загружаемых Python-пакетов всех времён
|
||||
|
||||
|
||||
Использовать его очень просто. Например, чтобы выполнить запрос `GET`, Вы бы написали:
|
||||
Пользоваться им очень просто. Например, чтобы сделать запрос `GET`, вы бы написали:
|
||||
|
||||
```Python
|
||||
response = requests.get("http://example.com/some/url")
|
||||
```
|
||||
|
||||
Противоположная *операция пути* в FastAPI может выглядеть следующим образом:
|
||||
Соответствующая в FastAPI API-операция пути могла бы выглядеть так:
|
||||
|
||||
```Python hl_lines="1"
|
||||
@app.get("/some/url")
|
||||
@@ -106,428 +96,390 @@ def read_url():
|
||||
return {"message": "Hello World"}
|
||||
```
|
||||
|
||||
Глядите, как похоже `requests.get(...)` и `@app.get(...)`.
|
||||
Посмотрите, насколько похожи `requests.get(...)` и `@app.get(...)`.
|
||||
|
||||
/// check | Идеи для **FastAPI**
|
||||
/// check | Вдохновило **FastAPI** на
|
||||
|
||||
* Должен быть простой и понятный API.
|
||||
* Нужно использовать названия HTTP-методов (операций) для упрощения понимания происходящего.
|
||||
* Должны быть разумные настройки по умолчанию и широкие возможности их кастомизации.
|
||||
* Иметь простой и понятный API.
|
||||
* Использовать названия HTTP-методов (операций) напрямую, простым и интуитивным образом.
|
||||
* Иметь разумные значения по умолчанию, но и мощные настройки.
|
||||
|
||||
///
|
||||
|
||||
### <a href="https://swagger.io/" class="external-link" target="_blank">Swagger</a> / <a href="https://github.com/OAI/OpenAPI-Specification/" class="external-link" target="_blank">OpenAPI</a>
|
||||
### <a href="https://swagger.io/" class="external-link" target="_blank">Swagger</a> / <a href="https://github.com/OAI/OpenAPI-Specification/" class="external-link" target="_blank">OpenAPI</a> { #swagger-openapi }
|
||||
|
||||
Главной функцией, которую я хотел унаследовать от Django REST Framework, была автоматическая документация API.
|
||||
Главной возможностью, которую хотелось взять из Django REST Framework, была автоматическая документация API.
|
||||
|
||||
Но потом я обнаружил, что существует стандарт документирования API, использующий JSON (или YAML, расширение JSON) под названием Swagger.
|
||||
Затем я обнаружил, что есть стандарт для документирования API с использованием JSON (или YAML — расширения JSON), под названием Swagger.
|
||||
|
||||
И к нему уже был создан пользовательский веб-интерфейс.
|
||||
Таким образом, возможность генерировать документацию Swagger для API позволила бы использовать этот интерфейс.
|
||||
И уже существовал веб-интерфейс для Swagger API. Поэтому возможность генерировать документацию Swagger для API позволила бы автоматически использовать этот веб-интерфейс.
|
||||
|
||||
В какой-то момент Swagger был передан Linux Foundation и переименован в OpenAPI.
|
||||
|
||||
Вот почему, когда говорят о версии 2.0, обычно говорят "Swagger", а для версии 3+ "OpenAPI".
|
||||
Вот почему, говоря о версии 2.0, обычно говорят «Swagger», а о версии 3+ — «OpenAPI».
|
||||
|
||||
/// check | Идеи для **FastAPI**
|
||||
/// check | Вдохновило **FastAPI** на
|
||||
|
||||
Использовать открытые стандарты для спецификаций API вместо самодельных схем.
|
||||
Использовать открытый стандарт для спецификаций API вместо самодельной схемы.
|
||||
|
||||
Совместимость с основанными на стандартах пользовательскими интерфейсами:
|
||||
И интегрировать основанные на стандартах инструменты пользовательского интерфейса:
|
||||
|
||||
* <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>
|
||||
* <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>
|
||||
|
||||
Они были выбраны за популярность и стабильность.
|
||||
Но сделав беглый поиск, Вы можете найти десятки альтернативных пользовательских интерфейсов для OpenAPI, которые Вы можете использовать с **FastAPI**.
|
||||
Эти два инструмента выбраны за популярность и стабильность, но даже при беглом поиске можно найти десятки альтернативных интерфейсов для OpenAPI (которые можно использовать с **FastAPI**).
|
||||
|
||||
///
|
||||
|
||||
### REST фреймворки для Flask
|
||||
### REST-фреймворки для Flask { #flask-rest-frameworks }
|
||||
|
||||
Существует несколько REST фреймворков для Flask, но потратив время и усилия на их изучение, я обнаружил, что многие из них не обновляются или заброшены и имеют нерешённые проблемы из-за которых они непригодны к использованию.
|
||||
Существует несколько REST-фреймворков для Flask, но, вложив время и усилия в исследование, я обнаружил, что многие из них прекращены или заброшены, с несколькими нерешёнными Issue (тикет\обращение), из-за которых они непригодны.
|
||||
|
||||
### <a href="https://marshmallow.readthedocs.io/en/stable/" class="external-link" target="_blank">Marshmallow</a>
|
||||
### <a href="https://marshmallow.readthedocs.io/en/stable/" class="external-link" target="_blank">Marshmallow</a> { #marshmallow }
|
||||
|
||||
Одной из основных функций, необходимых системам API, является "<abbr title="также называют маршаллингом или преобразованием">сериализация</abbr>" данных, то есть преобразование данных из кода (Python) во что-то, что может быть отправлено по сети.
|
||||
Например, превращение объекта содержащего данные из базы данных в объект JSON, конвертация объекта `datetime` в строку и т.п.
|
||||
Одна из основных возможностей, нужных системам API, — «<abbr title="также называемая маршаллингом или преобразованием">сериализация</abbr>» данных, то есть преобразование данных из кода (Python) во что-то, что можно отправить по сети. Например, преобразование объекта с данными из базы в JSON-объект. Преобразование объектов `datetime` в строки и т. п.
|
||||
|
||||
Еще одна важная функция, необходимая API — проверка данных, позволяющая убедиться, что данные действительны и соответствуют заданным параметрам.
|
||||
Как пример, можно указать, что ожидаются данные типа `int`, а не какая-то произвольная строка.
|
||||
Это особенно полезно для входящих данных.
|
||||
Ещё одна важная возможность, востребованная API, — валидация данных: убеждаться, что данные валидны с учётом заданных параметров. Например, что какое-то поле — `int`, а не произвольная строка. Это особенно полезно для входящих данных.
|
||||
|
||||
Без системы проверки данных Вам пришлось бы прописывать все проверки вручную.
|
||||
Без системы валидации данных вам пришлось бы выполнять все проверки вручную в коде.
|
||||
|
||||
Именно для обеспечения этих функций и была создана Marshmallow.
|
||||
Это отличная библиотека и я много раз пользовался ею раньше.
|
||||
Именно для этих возможностей и был создан Marshmallow. Это отличная библиотека, я много ей пользовался раньше.
|
||||
|
||||
Но она была создана до того, как появились подсказки типов Python.
|
||||
Итак, чтобы определить каждую <abbr title="Формат данных">схему</abbr>,
|
||||
Вам нужно использовать определенные утилиты и классы, предоставляемые Marshmallow.
|
||||
Но она появилась до того, как в Python появились аннотации типов. Поэтому для определения каждой <abbr title="описание того, как данные должны быть сформированы">схемы</abbr> нужно использовать специальные утилиты и классы, предоставляемые Marshmallow.
|
||||
|
||||
/// check | Идея для **FastAPI**
|
||||
/// check | Вдохновило **FastAPI** на
|
||||
|
||||
Использовать код программы для автоматического создания "схем", определяющих типы данных и их проверку.
|
||||
Использовать код для автоматического определения «схем», задающих типы данных и их валидацию.
|
||||
|
||||
///
|
||||
|
||||
### <a href="https://webargs.readthedocs.io/en/latest/" class="external-link" target="_blank">Webargs</a>
|
||||
### <a href="https://webargs.readthedocs.io/en/latest/" class="external-link" target="_blank">Webargs</a> { #webargs }
|
||||
|
||||
Другая немаловажная функция API - <abbr title="чтение и преобразование данных в объекты Python">парсинг</abbr> данных из входящих запросов.
|
||||
Ещё одна важная возможность для API — <abbr title="чтение и преобразование данных в объекты Python">парсинг</abbr> данных из входящих HTTP-запросов.
|
||||
|
||||
Webargs - это инструмент, который был создан для этого и поддерживает несколько фреймворков, включая Flask.
|
||||
Webargs — это инструмент, созданный для этого поверх нескольких фреймворков, включая Flask.
|
||||
|
||||
Для проверки данных он использует Marshmallow и создан теми же авторами.
|
||||
Он использует Marshmallow для валидации данных. И создан теми же разработчиками.
|
||||
|
||||
Это превосходный инструмент и я тоже часто пользовался им до **FastAPI**.
|
||||
Это отличный инструмент, и я тоже много им пользовался до появления **FastAPI**.
|
||||
|
||||
/// info | Информация
|
||||
|
||||
Webargs бы создан разработчиками Marshmallow.
|
||||
Webargs был создан теми же разработчиками, что и Marshmallow.
|
||||
|
||||
///
|
||||
|
||||
/// check | Идея для **FastAPI**
|
||||
/// check | Вдохновило **FastAPI** на
|
||||
|
||||
Должна быть автоматическая проверка входных данных.
|
||||
Автоматическую валидацию входящих данных HTTP-запроса.
|
||||
|
||||
///
|
||||
|
||||
### <a href="https://apispec.readthedocs.io/en/stable/" class="external-link" target="_blank">APISpec</a>
|
||||
### <a href="https://apispec.readthedocs.io/en/stable/" class="external-link" target="_blank">APISpec</a> { #apispec }
|
||||
|
||||
Marshmallow и Webargs осуществляют проверку, анализ и сериализацию данных как плагины.
|
||||
Marshmallow и Webargs предоставляют валидацию, парсинг и сериализацию как плагины.
|
||||
|
||||
Но документации API всё ещё не было. Тогда был создан APISpec.
|
||||
Но документации всё ещё не было. Тогда появился APISpec.
|
||||
|
||||
Это плагин для множества фреймворков, в том числе и для Starlette.
|
||||
Это плагин для многих фреймворков (есть плагин и для Starlette).
|
||||
|
||||
Он работает так - Вы записываете определение схем, используя формат YAML, внутри докстринга каждой функции, обрабатывающей маршрут.
|
||||
Он работает так: вы пишете определение схемы в формате YAML внутри докстринга каждой функции, обрабатывающей маршрут.
|
||||
|
||||
Используя эти докстринги, он генерирует схему OpenAPI.
|
||||
И он генерирует схемы OpenAPI.
|
||||
|
||||
Так это работает для Flask, Starlette, Responder и т.п.
|
||||
Так это работает во Flask, Starlette, Responder и т. д.
|
||||
|
||||
Но теперь у нас возникает новая проблема - наличие постороннего микро-синтаксиса внутри кода Python (большие YAML).
|
||||
Но у нас снова возникает проблема: появляется микро-синтаксис внутри строки Python (большой YAML).
|
||||
|
||||
Редактор кода не особо может помочь в такой парадигме.
|
||||
А изменив какие-то параметры или схемы для Marshmallow можно забыть отредактировать докстринг с YAML и сгенерированная схема становится недействительной.
|
||||
Редактор кода мало чем может помочь. И если мы изменим параметры или схемы Marshmallow и забудем также изменить YAML в докстринге, сгенерированная схема устареет.
|
||||
|
||||
/// info | Информация
|
||||
|
||||
APISpec тоже был создан авторами Marshmallow.
|
||||
APISpec был создан теми же разработчиками, что и Marshmallow.
|
||||
|
||||
///
|
||||
|
||||
/// check | Идея для **FastAPI**
|
||||
/// check | Вдохновило **FastAPI** на
|
||||
|
||||
Необходима поддержка открытого стандарта для API - OpenAPI.
|
||||
Поддержку открытого стандарта для API — OpenAPI.
|
||||
|
||||
///
|
||||
|
||||
### <a href="https://flask-apispec.readthedocs.io/en/latest/" class="external-link" target="_blank">Flask-apispec</a>
|
||||
### <a href="https://flask-apispec.readthedocs.io/en/latest/" class="external-link" target="_blank">Flask-apispec</a> { #flask-apispec }
|
||||
|
||||
Это плагин для Flask, который связан с Webargs, Marshmallow и APISpec.
|
||||
Это плагин для Flask, который связывает Webargs, Marshmallow и APISpec.
|
||||
|
||||
Он получает информацию от Webargs и Marshmallow, а затем использует APISpec для автоматического создания схемы OpenAPI.
|
||||
Он использует информацию из Webargs и Marshmallow, чтобы автоматически генерировать схемы OpenAPI с помощью APISpec.
|
||||
|
||||
Это отличный, но крайне недооценённый инструмент.
|
||||
Он должен быть более популярен, чем многие плагины для Flask.
|
||||
Возможно, это связано с тем, что его документация слишком скудна и абстрактна.
|
||||
Отличный и недооценённый инструмент. Он заслуживает большей популярности, чем многие плагины для Flask. Возможно, из-за слишком краткой и абстрактной документации.
|
||||
|
||||
Он избавил от необходимости писать чужеродный синтаксис YAML внутри докстрингов.
|
||||
Это решило проблему необходимости писать YAML (ещё один синтаксис) в докстрингах Python.
|
||||
|
||||
Такое сочетание Flask, Flask-apispec, Marshmallow и Webargs было моим любимым стеком при построении бэкенда до появления **FastAPI**.
|
||||
Комбинация Flask, Flask-apispec с Marshmallow и Webargs была моим любимым бэкенд-стеком до создания **FastAPI**.
|
||||
|
||||
Использование этого стека привело к созданию нескольких генераторов проектов. Я и некоторые другие команды до сих пор используем их:
|
||||
Его использование привело к созданию нескольких full-stack генераторов на Flask. Это основные стеки, которые я (и несколько внешних команд) использовали до сих пор:
|
||||
|
||||
* <a href="https://github.com/tiangolo/full-stack" class="external-link" target="_blank">https://github.com/tiangolo/full-stack</a>
|
||||
* <a href="https://github.com/tiangolo/full-stack-flask-couchbase" class="external-link" target="_blank">https://github.com/tiangolo/full-stack-flask-couchbase</a>
|
||||
* <a href="https://github.com/tiangolo/full-stack-flask-couchdb" class="external-link" target="_blank">https://github.com/tiangolo/full-stack-flask-couchdb</a>
|
||||
|
||||
Эти генераторы проектов также стали основой для [Генераторов проектов с **FastAPI**](project-generation.md){.internal-link target=_blank}.
|
||||
И эти же full-stack генераторы стали основой для [Генераторов проектов **FastAPI**](project-generation.md){.internal-link target=_blank}.
|
||||
|
||||
/// info | Информация
|
||||
|
||||
Как ни странно, но Flask-apispec тоже создан авторами Marshmallow.
|
||||
Flask-apispec был создан теми же разработчиками, что и Marshmallow.
|
||||
|
||||
///
|
||||
|
||||
/// check | Идея для **FastAPI**
|
||||
/// check | Вдохновило **FastAPI** на
|
||||
|
||||
Схема OpenAPI должна создаваться автоматически и использовать тот же код, который осуществляет сериализацию и проверку данных.
|
||||
Автоматическую генерацию схемы OpenAPI из того же кода, который определяет сериализацию и валидацию.
|
||||
|
||||
///
|
||||
|
||||
### <a href="https://nestjs.com/" class="external-link" target="_blank">NestJS</a> (и <a href="https://angular.io/" class="external-link" target="_blank">Angular</a>)
|
||||
### <a href="https://nestjs.com/" class="external-link" target="_blank">NestJS</a> (и <a href="https://angular.io/" class="external-link" target="_blank">Angular</a>) { #nestjs-and-angular }
|
||||
|
||||
Здесь даже не используется Python. NestJS - этот фреймворк написанный на JavaScript (TypeScript), основанный на NodeJS и вдохновлённый Angular.
|
||||
Это даже не Python. NestJS — это JavaScript/TypeScript-фреймворк на NodeJS, вдохновлённый Angular.
|
||||
|
||||
Он позволяет получить нечто похожее на то, что можно сделать с помощью Flask-apispec.
|
||||
Он достигает чего-то отчасти похожего на то, что можно сделать с Flask-apispec.
|
||||
|
||||
В него встроена система внедрения зависимостей, ещё одна идея взятая от Angular.
|
||||
Однако требуется предварительная регистрация "внедрений" (как и во всех других известных мне системах внедрения зависимостей), что увеличивает количество и повторяемость кода.
|
||||
В нём встроена система внедрения зависимостей, вдохновлённая Angular 2. Требуется предварительная регистрация «инжектируемых» компонентов (как и во всех известных мне системах внедрения зависимостей), что добавляет многословности и повторяемости кода.
|
||||
|
||||
Так как параметры в нём описываются с помощью типов TypeScript (аналогично подсказкам типов в Python), поддержка редактора работает довольно хорошо.
|
||||
Поскольку параметры описываются с помощью типов TypeScript (аналог аннотаций типов в Python), поддержка редактора весьма хороша.
|
||||
|
||||
Но поскольку данные из TypeScript не сохраняются после компиляции в JavaScript, он не может полагаться на подсказки типов для определения проверки данных, сериализации и документации.
|
||||
Из-за этого и некоторых дизайнерских решений, для валидации, сериализации и автоматической генерации схем, приходится во многих местах добавлять декораторы.
|
||||
Таким образом, это становится довольно многословным.
|
||||
Но так как данные о типах TypeScript не сохраняются после компиляции в JavaScript, он не может полагаться на типы для одновременного определения валидации, сериализации и документации. Из‑за этого и некоторых проектных решений для получения валидации, сериализации и автоматической генерации схем приходится добавлять декораторы во многих местах. В итоге это становится довольно многословным.
|
||||
|
||||
Кроме того, он не очень хорошо справляется с вложенными моделями.
|
||||
Если в запросе имеется объект JSON, внутренние поля которого, в свою очередь, являются вложенными объектами JSON, это не может быть должным образом задокументировано и проверено.
|
||||
Он плохо справляется с вложенными моделями. Если JSON-тело запроса — это объект JSON, содержащий внутренние поля, которые сами являются вложенными объектами JSON, это нельзя как следует задокументировать и провалидировать.
|
||||
|
||||
/// check | Идеи для **FastAPI**
|
||||
/// check | Вдохновило **FastAPI** на
|
||||
|
||||
Нужно использовать подсказки типов, чтоб воспользоваться поддержкой редактора кода.
|
||||
Использовать типы Python для отличной поддержки в редакторе кода.
|
||||
|
||||
Нужна мощная система внедрения зависимостей. Необходим способ для уменьшения повторов кода.
|
||||
Иметь мощную систему внедрения зависимостей. Найти способ минимизировать повторение кода.
|
||||
|
||||
///
|
||||
|
||||
### <a href="https://sanic.readthedocs.io/en/latest/" class="external-link" target="_blank">Sanic</a>
|
||||
### <a href="https://sanic.readthedocs.io/en/latest/" class="external-link" target="_blank">Sanic</a> { #sanic }
|
||||
|
||||
Sanic был одним из первых чрезвычайно быстрых Python-фреймворков основанных на `asyncio`.
|
||||
Он был сделан очень похожим на Flask.
|
||||
Это был один из первых чрезвычайно быстрых Python-фреймворков на основе `asyncio`. Он был сделан очень похожим на Flask.
|
||||
|
||||
/// note | Технические детали
|
||||
|
||||
В нём использован <a href="https://github.com/MagicStack/uvloop" class="external-link" target="_blank">`uvloop`</a> вместо стандартного цикла событий `asyncio`, что и сделало его таким быстрым.
|
||||
Он использовал <a href="https://github.com/MagicStack/uvloop" class="external-link" target="_blank">`uvloop`</a> вместо стандартного цикла `asyncio` в Python. Это и сделало его таким быстрым.
|
||||
|
||||
Он явно вдохновил создателей Uvicorn и Starlette, которые в настоящее время быстрее Sanic в открытых бенчмарках.
|
||||
Он явно вдохновил Uvicorn и Starlette, которые сейчас быстрее Sanic в открытых бенчмарках.
|
||||
|
||||
///
|
||||
|
||||
/// check | Идеи для **FastAPI**
|
||||
/// check | Вдохновило **FastAPI** на
|
||||
|
||||
Должна быть сумасшедшая производительность.
|
||||
Поиск способа достичь сумасшедшей производительности.
|
||||
|
||||
Для этого **FastAPI** основан на Starlette, самом быстром из доступных фреймворков (по замерам незаинтересованных лиц).
|
||||
Именно поэтому **FastAPI** основан на Starlette, так как это самый быстрый доступный фреймворк (по данным сторонних бенчмарков).
|
||||
|
||||
///
|
||||
|
||||
### <a href="https://falconframework.org/" class="external-link" target="_blank">Falcon</a>
|
||||
### <a href="https://falconframework.org/" class="external-link" target="_blank">Falcon</a> { #falcon }
|
||||
|
||||
Falcon - ещё один высокопроизводительный Python-фреймворк.
|
||||
В нём минимум функций и он создан, чтоб быть основой для других фреймворков, например, Hug.
|
||||
Falcon — ещё один высокопроизводительный Python-фреймворк, он минималистичен и служит основой для других фреймворков, таких как Hug.
|
||||
|
||||
Функции в нём получают два параметра - "запрос к серверу" и "ответ сервера".
|
||||
Затем Вы "читаете" часть запроса и "пишите" часть ответа.
|
||||
Из-за такой конструкции невозможно объявить параметры запроса и тела сообщения со стандартными подсказками типов Python в качестве параметров функции.
|
||||
Он спроектирован так, что функции получают два параметра: «request» и «response». Затем вы «читаете» части из запроса и «пишете» части в ответ. Из‑за такого дизайна невозможно объявить параметры запроса и тело запроса стандартными аннотациями типов Python как параметры функции.
|
||||
|
||||
Таким образом, и валидацию данных, и их сериализацию, и документацию нужно прописывать вручную.
|
||||
Либо эти функции должны быть встроены во фреймворк, сконструированный поверх Falcon, как в Hug.
|
||||
Такая же особенность присутствует и в других фреймворках, вдохновлённых идеей Falcon, использовать только один объект запроса и один объект ответа.
|
||||
Поэтому валидация данных, сериализация и документация должны выполняться в коде вручную, не автоматически. Либо должны быть реализованы во фреймворке поверх Falcon, как в Hug. Та же особенность есть и в других фреймворках, вдохновлённых дизайном Falcon — с одним объектом запроса и одним объектом ответа в параметрах.
|
||||
|
||||
/// check | Идея для **FastAPI**
|
||||
/// check | Вдохновило **FastAPI** на
|
||||
|
||||
Найдите способы добиться отличной производительности.
|
||||
Поиск способов получить отличную производительность.
|
||||
|
||||
Объявлять параметры `ответа сервера` в функциях, как в Hug.
|
||||
Вместе с Hug (так как Hug основан на Falcon) вдохновило **FastAPI** объявлять параметр `response` в функциях.
|
||||
|
||||
Хотя в FastAPI это необязательно и используется в основном для установки заголовков, куки и альтернативных кодов состояния.
|
||||
Хотя в FastAPI это необязательно, и используется в основном для установки HTTP-заголовков, cookie и альтернативных статус-кодов.
|
||||
|
||||
///
|
||||
|
||||
### <a href="https://moltenframework.com/" class="external-link" target="_blank">Molten</a>
|
||||
### <a href="https://moltenframework.com/" class="external-link" target="_blank">Molten</a> { #molten }
|
||||
|
||||
Molten мне попался на начальной стадии написания **FastAPI**. В нём были похожие идеи:
|
||||
Я обнаружил Molten на ранних этапах создания **FastAPI**. И у него были очень похожие идеи:
|
||||
|
||||
* Использование подсказок типов.
|
||||
* Валидация и документация исходя из этих подсказок.
|
||||
* Основан на аннотациях типов Python.
|
||||
* Валидация и документация из этих типов.
|
||||
* Система внедрения зависимостей.
|
||||
|
||||
В нём не используются сторонние библиотеки (такие, как Pydantic) для валидации, сериализации и документации.
|
||||
Поэтому переиспользовать эти определения типов непросто.
|
||||
Он не использует стороннюю библиотеку для валидации, сериализации и документации, такую как Pydantic, — у него своя. Поэтому такие определения типов данных будет сложнее переиспользовать.
|
||||
|
||||
Также требуется более подробная конфигурация и используется стандарт WSGI, который не предназначен для использования с высокопроизводительными инструментами, такими как Uvicorn, Starlette и Sanic, в отличие от ASGI.
|
||||
Требуются более многословные конфигурации. И так как он основан на WSGI (вместо ASGI), он не предназначен для использования преимуществ высокой производительности инструментов вроде Uvicorn, Starlette и Sanic.
|
||||
|
||||
Его система внедрения зависимостей требует предварительной регистрации, и зависимости определяются, как объявления типов.
|
||||
Из-за этого невозможно объявить более одного "компонента" (зависимости), который предоставляет определенный тип.
|
||||
Система внедрения зависимостей требует предварительной регистрации зависимостей, а зависимости разрешаются по объявленным типам. Поэтому невозможно объявить более одного «компонента», предоставляющего определённый тип.
|
||||
|
||||
Маршруты объявляются в единственном месте с использованием функций, объявленных в других местах (вместо использования декораторов, в которые могут быть обёрнуты функции, обрабатывающие конкретные ресурсы).
|
||||
Это больше похоже на Django, чем на Flask и Starlette.
|
||||
Он разделяет в коде вещи, которые довольно тесно связаны.
|
||||
Маршруты объявляются в одном месте, используя функции, объявленные в других местах (вместо декораторов, которые можно разместить прямо над функцией, обрабатывающей эндпоинт). Это ближе к тому, как это делает Django, чем Flask (и Starlette). Это разделяет в коде вещи, которые довольно тесно связаны.
|
||||
|
||||
/// check | Идея для **FastAPI**
|
||||
/// check | Вдохновило **FastAPI** на
|
||||
|
||||
Определить дополнительные проверки типов данных, используя значения атрибутов модели "по умолчанию".
|
||||
Это улучшает помощь редактора и раньше это не было доступно в Pydantic.
|
||||
Определять дополнительные проверки типов данных, используя значение «по умолчанию» атрибутов модели. Это улучшает поддержку в редакторе кода, и раньше этого не было в Pydantic.
|
||||
|
||||
Фактически это подтолкнуло на обновление Pydantic для поддержки одинакового стиля проверок (теперь этот функционал уже доступен в Pydantic).
|
||||
Фактически это вдохновило на обновление частей Pydantic, чтобы поддерживать такой же стиль объявления валидации (вся эта функциональность теперь уже есть в Pydantic).
|
||||
|
||||
///
|
||||
|
||||
### <a href="https://github.com/hugapi/hug" class="external-link" target="_blank">Hug</a>
|
||||
### <a href="https://github.com/hugapi/hug" class="external-link" target="_blank">Hug</a> { #hug }
|
||||
|
||||
Hug был одним из первых фреймворков, реализовавших объявление параметров API с использованием подсказок типов Python.
|
||||
Эта отличная идея была использована и другими инструментами.
|
||||
Hug был одним из первых фреймворков, реализовавших объявление типов параметров API с использованием аннотаций типов Python. Это была отличная идея, которая вдохновила и другие инструменты.
|
||||
|
||||
При объявлении параметров вместо стандартных типов Python использовались собственные типы, но всё же это был огромный шаг вперед.
|
||||
Он использовал собственные типы в объявлениях вместо стандартных типов Python, но это всё равно был огромный шаг вперёд.
|
||||
|
||||
Это также был один из первых фреймворков, генерировавших полную API-схему в формате JSON.
|
||||
Он также был одним из первых фреймворков, генерировавших собственную схему, описывающую весь API в JSON.
|
||||
|
||||
Данная схема не придерживалась стандартов вроде OpenAPI и JSON Schema.
|
||||
Поэтому было бы непросто совместить её с другими инструментами, такими как Swagger UI.
|
||||
Но опять же, это была очень инновационная идея.
|
||||
Он не был основан на стандартах вроде OpenAPI и JSON Schema. Поэтому интегрировать его с другими инструментами, такими как Swagger UI, было бы непросто. Но, опять же, это была очень инновационная идея.
|
||||
|
||||
Ещё у него есть интересная и необычная функция: используя один и тот же фреймворк можно создавать и API, и <abbr title="Интерфейс командной строки">CLI</abbr>.
|
||||
У него есть интересная и необычная особенность: с помощью одного и того же фреймворка можно создавать и API, и CLI.
|
||||
|
||||
Поскольку он основан на WSGI, старом стандарте для синхронных веб-фреймворков, он не может работать с веб-сокетами и другими модными штуками, но всё равно обладает высокой производительностью.
|
||||
Так как он основан на предыдущем стандарте для синхронных веб-фреймворков Python (WSGI), он не может работать с WebSocket и прочим, хотя также демонстрирует высокую производительность.
|
||||
|
||||
/// info | Информация
|
||||
|
||||
Hug создан Timothy Crosley, автором <a href="https://github.com/timothycrosley/isort" class="external-link" target="_blank">`isort`</a>, отличного инструмента для автоматической сортировки импортов в Python-файлах.
|
||||
Hug был создан Тимоти Кросли, тем же автором <a href="https://github.com/timothycrosley/isort" class="external-link" target="_blank">`isort`</a>, отличного инструмента для автоматической сортировки импортов в файлах Python.
|
||||
|
||||
///
|
||||
|
||||
/// check | Идеи для **FastAPI**
|
||||
/// check | Идеи, вдохновившие **FastAPI**
|
||||
|
||||
Hug повлиял на создание некоторых частей APIStar и был одним из инструментов, которые я счел наиболее многообещающими, наряду с APIStar.
|
||||
Hug вдохновил части APIStar и был одним из наиболее многообещающих инструментов, которые я нашёл, наряду с APIStar.
|
||||
|
||||
Hug натолкнул на мысли использовать в **FastAPI** подсказки типов Python для автоматического создания схемы, определяющей API и его параметры.
|
||||
Hug помог вдохновить **FastAPI** использовать аннотации типов Python для объявления параметров и автоматически генерировать схему, определяющую API.
|
||||
|
||||
Hug вдохновил **FastAPI** объявить параметр `ответа` в функциях для установки заголовков и куки.
|
||||
Hug вдохновил **FastAPI** объявлять параметр `response` в функциях для установки HTTP-заголовков и cookie.
|
||||
|
||||
///
|
||||
|
||||
### <a href="https://github.com/encode/apistar" class="external-link" target="_blank">APIStar</a> (<= 0.5)
|
||||
### <a href="https://github.com/encode/apistar" class="external-link" target="_blank">APIStar</a> (<= 0.5) { #apistar-0-5 }
|
||||
|
||||
Непосредственно перед тем, как принять решение о создании **FastAPI**, я обнаружил **APIStar**.
|
||||
В нем было почти все, что я искал и у него был отличный дизайн.
|
||||
Прямо перед решением строить **FastAPI** я нашёл сервер **APIStar**. В нём было почти всё, что я искал, и отличный дизайн.
|
||||
|
||||
Это была одна из первых реализаций фреймворка, использующего подсказки типов для объявления параметров и запросов, которые я когда-либо видел (до NestJS и Molten).
|
||||
Я нашёл его примерно в то же время, что и Hug, но APIStar использовал стандарт OpenAPI.
|
||||
Это была одна из первых реализаций фреймворка, использующего аннотации типов Python для объявления параметров и запросов (до NestJS и Molten), которые я видел. Я обнаружил его примерно в то же время, что и Hug. Но APIStar использовал стандарт OpenAPI.
|
||||
|
||||
В нём были автоматические проверка и сериализация данных и генерация схемы OpenAPI основанные на подсказках типов в нескольких местах.
|
||||
В нём были автоматические валидация данных, сериализация данных и генерация схемы OpenAPI на основе тех же аннотаций типов в нескольких местах.
|
||||
|
||||
При определении схемы тела сообщения не использовались подсказки типов, как в Pydantic, это больше похоже на Marshmallow, поэтому помощь редактора была недостаточно хорошей, но всё же APIStar был лучшим доступным вариантом.
|
||||
Определение схемы тела запроса не использовало те же аннотации типов Python, как в Pydantic, — это было ближе к Marshmallow, поэтому поддержка редактора была бы хуже, но всё равно APIStar оставался лучшим доступным вариантом.
|
||||
|
||||
На тот момент у него были лучшие показатели производительности (проигрывающие только Starlette).
|
||||
На тот момент у него были лучшие показатели в бенчмарках (его превосходил только Starlette).
|
||||
|
||||
Изначально у него не было автоматической документации API для веб-интерфейса, но я знал, что могу добавить к нему Swagger UI.
|
||||
Сначала у него не было веб‑UI для автоматической документации API, но я знал, что могу добавить к нему Swagger UI.
|
||||
|
||||
В APIStar была система внедрения зависимостей, которая тоже требовала предварительную регистрацию компонентов, как и ранее описанные инструменты.
|
||||
Но, тем не менее, это была отличная штука.
|
||||
У него была система внедрения зависимостей. Она требовала предварительной регистрации компонентов, как и другие инструменты, обсуждавшиеся выше. Но всё же это была отличная возможность.
|
||||
|
||||
Я не смог использовать его в полноценном проекте, так как были проблемы со встраиванием <abbr title="Авторизация и соответствующие допуски к операциям пути (эндпоинтам)">функций безопасности</abbr> в схему OpenAPI, из-за которых невозможно было встроить все функции, применяемые в генераторах проектов на основе Flask-apispec.
|
||||
Я добавил в свой список задач создание пул-реквеста, добавляющего эту функциональность.
|
||||
Мне так и не удалось использовать его в полном проекте, поскольку не было интеграции с системой безопасности, поэтому я не мог заменить все возможности, которые имел с full-stack генераторами на основе Flask-apispec. В моём бэклоге было создать пулл-реквест (запрос на изменение), добавляющий эту функциональность.
|
||||
|
||||
В дальнейшем фокус проекта сместился.
|
||||
Затем фокус проекта сместился.
|
||||
|
||||
Это больше не был API-фреймворк, так как автор сосредоточился на Starlette.
|
||||
Это перестал быть веб-фреймворк для API, так как автору нужно было сосредоточиться на Starlette.
|
||||
|
||||
Ныне APIStar - это набор инструментов для проверки спецификаций OpenAPI.
|
||||
Сейчас APIStar — это набор инструментов для валидации спецификаций OpenAPI, а не веб-фреймворк.
|
||||
|
||||
/// info | Информация
|
||||
|
||||
APIStar был создан Tom Christie. Тот самый парень, который создал:
|
||||
APIStar был создан Томом Кристи. Тем самым человеком, который создал:
|
||||
|
||||
* Django REST Framework
|
||||
* Starlette (на котором основан **FastAPI**)
|
||||
* Uvicorn (используемый в Starlette и **FastAPI**)
|
||||
* Uvicorn (используется Starlette и **FastAPI**)
|
||||
|
||||
///
|
||||
|
||||
/// check | Идеи для **FastAPI**
|
||||
/// check | Вдохновило **FastAPI** на
|
||||
|
||||
Воплощение.
|
||||
Существование.
|
||||
|
||||
Мне казалось блестящей идеей объявлять множество функций (проверка данных, сериализация, документация) с помощью одних и тех же типов Python, которые при этом обеспечивают ещё и помощь редактора кода.
|
||||
Идея объявлять сразу несколько вещей (валидацию данных, сериализацию и документацию) с помощью одних и тех же типов Python, которые одновременно обеспечивают отличную поддержку в редакторе кода, показалась мне блестящей.
|
||||
|
||||
После долгих поисков среди похожих друг на друга фреймворков и сравнения их различий, APIStar стал самым лучшим выбором.
|
||||
После долгих поисков похожего фреймворка и тестирования множества альтернатив APIStar был лучшим доступным вариантом.
|
||||
|
||||
Но APIStar перестал быть фреймворком для создания веб-сервера, зато появился Starlette, новая и лучшая основа для построения подобных систем.
|
||||
Это была последняя капля, сподвигнувшая на создание **FastAPI**.
|
||||
Затем APIStar перестал существовать как сервер, а был создан Starlette — новая и лучшая основа для такой системы. Это стало окончательным вдохновением для создания **FastAPI**.
|
||||
|
||||
Я считаю **FastAPI** "духовным преемником" APIStar, улучившим его возможности благодаря урокам, извлечённым из всех упомянутых выше инструментов.
|
||||
Я считаю **FastAPI** «духовным преемником» APIStar, который улучшает и расширяет возможности, систему типов и другие части, опираясь на уроки от всех этих предыдущих инструментов.
|
||||
|
||||
///
|
||||
|
||||
## Что используется в **FastAPI**
|
||||
## Что используется в **FastAPI** { #used-by-fastapi }
|
||||
|
||||
### <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a>
|
||||
### <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a> { #pydantic }
|
||||
|
||||
Pydantic - это библиотека для валидации данных, сериализации и документирования (используя JSON Schema), основываясь на подсказках типов Python, что делает его чрезвычайно интуитивным.
|
||||
Pydantic — это библиотека для определения валидации данных, сериализации и документации (с использованием JSON Schema) на основе аннотаций типов Python.
|
||||
|
||||
Его можно сравнить с Marshmallow, хотя в бенчмарках Pydantic быстрее, чем Marshmallow.
|
||||
И он основан на тех же подсказках типов, которые отлично поддерживаются редакторами кода.
|
||||
Благодаря этому он чрезвычайно интуитивен.
|
||||
|
||||
/// check | **FastAPI** использует Pydantic
|
||||
Его можно сравнить с Marshmallow. Хотя в бенчмарках он быстрее Marshmallow. И поскольку он основан на тех же аннотациях типов Python, поддержка в редакторе кода отличная.
|
||||
|
||||
Для проверки данных, сериализации данных и автоматической документации моделей (на основе JSON Schema).
|
||||
/// check | **FastAPI** использует его для
|
||||
|
||||
Затем **FastAPI** берёт эти схемы JSON и помещает их в схему OpenAPI, не касаясь других вещей, которые он делает.
|
||||
Обработки всей валидации данных, сериализации данных и автоматической документации моделей (на основе JSON Schema).
|
||||
|
||||
Затем **FastAPI** берёт эти данные JSON Schema и помещает их в OpenAPI, помимо всех прочих функций.
|
||||
|
||||
///
|
||||
|
||||
### <a href="https://www.starlette.io/" class="external-link" target="_blank">Starlette</a>
|
||||
### <a href="https://www.starlette.io/" class="external-link" target="_blank">Starlette</a> { #starlette }
|
||||
|
||||
Starlette - это легковесный <abbr title="Новый стандарт построения асинхронных веб-сервисов Python">ASGI</abbr> фреймворк/набор инструментов, который идеален для построения высокопроизводительных асинхронных сервисов.
|
||||
Starlette — это лёгкий <abbr title="Новый стандарт построения асинхронных веб-сервисов Python">ASGI</abbr> фреймворк/набор инструментов, идеально подходящий для создания высокопроизводительных asyncio‑сервисов.
|
||||
|
||||
Starlette очень простой и интуитивный.
|
||||
Он разработан таким образом, чтобы быть легко расширяемым и иметь модульные компоненты.
|
||||
Он очень простой и интуитивный. Спроектирован так, чтобы его было легко расширять, и чтобы компоненты были модульными.
|
||||
|
||||
В нём есть:
|
||||
|
||||
* Впечатляющая производительность.
|
||||
* Поддержка веб-сокетов.
|
||||
* Фоновые задачи.
|
||||
* Обработка событий при старте и финише приложения.
|
||||
* Тестовый клиент на основе HTTPX.
|
||||
* Поддержка CORS, сжатие GZip, статические файлы, потоковая передача данных.
|
||||
* Поддержка сессий и куки.
|
||||
* Поддержка WebSocket.
|
||||
* Фоновые задачи, выполняемые в том же процессе.
|
||||
* События запуска и завершения.
|
||||
* Тестовый клиент на базе HTTPX.
|
||||
* CORS, GZip, статические файлы, потоковые ответы.
|
||||
* Поддержка сессий и cookie.
|
||||
* 100% покрытие тестами.
|
||||
* 100% аннотированный код.
|
||||
* Несколько жёстких зависимостей.
|
||||
* 100% кодовой базы с аннотациями типов.
|
||||
* Мало жёстких зависимостей.
|
||||
|
||||
В настоящее время Starlette показывает самую высокую скорость среди Python-фреймворков в тестовых замерах.
|
||||
Быстрее только Uvicorn, который является сервером, а не фреймворком.
|
||||
В настоящее время Starlette — самый быстрый из протестированных Python-фреймворков. Его превосходит только Uvicorn, который не фреймворк, а сервер.
|
||||
|
||||
Starlette обеспечивает весь функционал микрофреймворка, но не предоставляет автоматическую валидацию данных, сериализацию и документацию.
|
||||
Starlette предоставляет весь базовый функционал веб-микрофреймворка.
|
||||
|
||||
**FastAPI** добавляет эти функции используя подсказки типов Python и Pydantic.
|
||||
Ещё **FastAPI** добавляет систему внедрения зависимостей, утилиты безопасности, генерацию схемы OpenAPI и т.д.
|
||||
Но он не предоставляет автоматическую валидацию данных, сериализацию или документацию.
|
||||
|
||||
Это одна из главных вещей, которые **FastAPI** добавляет поверх, всё на основе аннотаций типов Python (с использованием Pydantic). Плюс система внедрения зависимостей, утилиты безопасности, генерация схемы OpenAPI и т. д.
|
||||
|
||||
/// note | Технические детали
|
||||
|
||||
ASGI - это новый "стандарт" разработанный участниками команды Django.
|
||||
Он пока что не является "стандартом в Python" (то есть принятым PEP), но процесс принятия запущен.
|
||||
ASGI — это новый «стандарт», разрабатываемый участниками core-команды Django. Он всё ещё не является «стандартом Python» (PEP), хотя процесс идёт.
|
||||
|
||||
Тем не менее он уже используется в качестве "стандарта" несколькими инструментами.
|
||||
Это значительно улучшает совместимость, поскольку Вы можете переключиться с Uvicorn на любой другой ASGI-сервер (например, Daphne или Hypercorn) или Вы можете добавить ASGI-совместимые инструменты, такие как `python-socketio`.
|
||||
Тем не менее его уже используют как «стандарт» несколько инструментов. Это сильно улучшает совместимость: вы можете заменить Uvicorn на любой другой ASGI-сервер (например, Daphne или Hypercorn) или добавить совместимые с ASGI инструменты, такие как `python-socketio`.
|
||||
|
||||
///
|
||||
|
||||
/// check | **FastAPI** использует Starlette
|
||||
/// check | **FastAPI** использует его для
|
||||
|
||||
В качестве ядра веб-сервиса для обработки запросов, добавив некоторые функции сверху.
|
||||
Обработки всех основных веб-частей. Добавляя возможности поверх.
|
||||
|
||||
Класс `FastAPI` наследуется напрямую от класса `Starlette`.
|
||||
Класс `FastAPI` напрямую наследуется от класса `Starlette`.
|
||||
|
||||
Таким образом, всё что Вы могли делать со Starlette, Вы можете делать с **FastAPI**, по сути это прокачанный Starlette.
|
||||
Так что всё, что вы можете сделать со Starlette, вы можете сделать напрямую с **FastAPI**, по сути это «Starlette на стероидах».
|
||||
|
||||
///
|
||||
|
||||
### <a href="https://www.uvicorn.org/" class="external-link" target="_blank">Uvicorn</a>
|
||||
### <a href="https://www.uvicorn.org/" class="external-link" target="_blank">Uvicorn</a> { #uvicorn }
|
||||
|
||||
Uvicorn - это молниеносный ASGI-сервер, построенный на uvloop и httptools.
|
||||
Uvicorn — молниеносный ASGI-сервер, построенный на uvloop и httptools.
|
||||
|
||||
Uvicorn является сервером, а не фреймворком.
|
||||
Например, он не предоставляет инструментов для маршрутизации запросов по ресурсам.
|
||||
Для этого нужна надстройка, такая как Starlette (или **FastAPI**).
|
||||
Это не веб-фреймворк, а сервер. Например, он не предоставляет инструменты для маршрутизации по путям. Это предоставляет сверху фреймворк, такой как Starlette (или **FastAPI**).
|
||||
|
||||
Он рекомендуется в качестве сервера для Starlette и **FastAPI**.
|
||||
Это рекомендуемый сервер для Starlette и **FastAPI**.
|
||||
|
||||
/// check | **FastAPI** рекомендует его
|
||||
/// check | **FastAPI** рекомендует его как
|
||||
|
||||
Как основной сервер для запуска приложения **FastAPI**.
|
||||
Основной веб-сервер для запуска приложений **FastAPI**.
|
||||
|
||||
Вы можете объединить его с Gunicorn, чтобы иметь асинхронный многопроцессный сервер.
|
||||
Также вы можете использовать опцию командной строки `--workers`, чтобы получить асинхронный многопроцессный сервер.
|
||||
|
||||
Узнать больше деталей можно в разделе [Развёртывание](deployment/index.md){.internal-link target=_blank}.
|
||||
Подробнее см. раздел [Развёртывание](deployment/index.md){.internal-link target=_blank}.
|
||||
|
||||
///
|
||||
|
||||
## Тестовые замеры и скорость
|
||||
## Бенчмарки и скорость { #benchmarks-and-speed }
|
||||
|
||||
Чтобы понять, сравнить и увидеть разницу между Uvicorn, Starlette и FastAPI, ознакомьтесь с разделом [Тестовые замеры](benchmarks.md){.internal-link target=_blank}.
|
||||
Чтобы понять, сравнить и увидеть разницу между Uvicorn, Starlette и FastAPI, см. раздел о [Бенчмарках](benchmarks.md){.internal-link target=_blank}.
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
# Конкурентность и async / await
|
||||
# Конкурентность и async / await { #concurrency-and-async-await }
|
||||
|
||||
Здесь приведена подробная информация об использовании синтаксиса `async def` при написании *функций обработки пути*, а также рассмотрены основы асинхронного программирования, конкурентности и параллелизма.
|
||||
Подробности о синтаксисе `async def` для *функций-обработчиков пути* и немного фона об асинхронном коде, конкурентности и параллелизме.
|
||||
|
||||
## Нет времени?<a name="in-a-hurry"></a>
|
||||
## Нет времени? { #in-a-hurry }
|
||||
|
||||
<abbr title="too long; didn't read (основная мысль)"><strong>TL;DR:</strong></abbr>
|
||||
<abbr title="too long; didn't read – слишком длинно; не читал"><strong>TL;DR:</strong></abbr>
|
||||
|
||||
Допустим, вы используете сторонюю библиотеку, которая требует вызова с ключевым словом `await`:
|
||||
Если вы используете сторонние библиотеки, которые нужно вызывать с `await`, например:
|
||||
|
||||
```Python
|
||||
results = await some_library()
|
||||
```
|
||||
|
||||
В этом случае *функции обработки пути* необходимо объявлять с использованием синтаксиса `async def`:
|
||||
Тогда объявляйте *функции-обработчики пути* с `async def`, например:
|
||||
|
||||
```Python hl_lines="2"
|
||||
@app.get('/')
|
||||
@@ -21,18 +21,15 @@ async def read_results():
|
||||
return results
|
||||
```
|
||||
|
||||
/// note
|
||||
/// note | Примечание
|
||||
|
||||
`await` можно использовать только внутри функций, объявленных с использованием `async def`.
|
||||
`await` можно использовать только внутри функций, объявленных с `async def`.
|
||||
|
||||
///
|
||||
|
||||
---
|
||||
|
||||
Если вы обращаетесь к сторонней библиотеке, которая с чем-то взаимодействует
|
||||
(с базой данных, API, файловой системой и т. д.), и не имеет поддержки синтаксиса `await`
|
||||
(что относится сейчас к большинству библиотек для работы с базами данных), то
|
||||
объявляйте *функции обработки пути* обычным образом с помощью `def`, например:
|
||||
Если вы используете стороннюю библиотеку, которая взаимодействует с чем-то (база данных, API, файловая система и т.д.) и не поддерживает использование `await` (сейчас это относится к большинству библиотек для БД), тогда объявляйте *функции-обработчики пути* как обычно, просто с `def`, например:
|
||||
|
||||
```Python hl_lines="2"
|
||||
@app.get('/')
|
||||
@@ -43,310 +40,283 @@ def results():
|
||||
|
||||
---
|
||||
|
||||
Если вашему приложению (странным образом) не нужно ни с чем взаимодействовать и, соответственно,
|
||||
ожидать ответа, используйте `async def`.
|
||||
Если вашему приложению (по какой-то причине) не нужно ни с чем взаимодействовать и ждать ответа, используйте `async def`, даже если внутри не нужен `await`.
|
||||
|
||||
---
|
||||
|
||||
Если вы не уверены, используйте обычный синтаксис `def`.
|
||||
Если вы просто не уверены, используйте обычный `def`.
|
||||
|
||||
---
|
||||
|
||||
**Примечание**: при необходимости можно смешивать `def` и `async def` в *функциях обработки пути*
|
||||
и использовать в каждом случае наиболее подходящий синтаксис. А FastAPI сделает с этим всё, что нужно.
|
||||
**Примечание**: вы можете смешивать `def` и `async def` в *функциях-обработчиках пути* столько, сколько нужно, и объявлять каждую так, как лучше для вашего случая. FastAPI сделает с ними всё как надо.
|
||||
|
||||
В любом из описанных случаев FastAPI работает асинхронно и очень быстро.
|
||||
В любом из случаев выше FastAPI всё равно работает асинхронно и очень быстро.
|
||||
|
||||
Однако придерживаясь указанных советов, можно получить дополнительную оптимизацию производительности.
|
||||
Но следуя этим шагам, он сможет выполнить некоторые оптимизации производительности.
|
||||
|
||||
## Технические подробности
|
||||
## Технические подробности { #technical-details }
|
||||
|
||||
Современные версии Python поддерживают разработку так называемого **"асинхронного кода"** посредством написания **"сопрограмм"** с использованием синтаксиса **`async` и `await`**.
|
||||
Современные версии Python поддерживают **«асинхронный код»** с помощью **«сопрограмм»** (coroutines) и синтаксиса **`async` и `await`**.
|
||||
|
||||
Ниже разберём эту фразу по частям:
|
||||
Разберём эту фразу по частям в разделах ниже:
|
||||
|
||||
* **Асинхронный код**
|
||||
* **`async` и `await`**
|
||||
* **Сопрограммы**
|
||||
|
||||
## Асинхронный код
|
||||
## Асинхронный код { #asynchronous-code }
|
||||
|
||||
Асинхронный код означает, что в языке 💬 есть возможность сообщить машине / программе 🤖,
|
||||
что в определённой точке кода ей 🤖 нужно будет ожидать завершения выполнения *чего-то ещё* в другом месте. Допустим это *что-то ещё* называется "медленный файл" 📝.
|
||||
Асинхронный код значит, что в языке 💬 есть способ сказать компьютеру/программе 🤖, что в некоторый момент кода ему 🤖 придётся подождать, пока *что-то ещё* где-то в другом месте завершится. Назовём это *что-то ещё* «медленный файл» 📝.
|
||||
|
||||
И пока мы ждём завершения работы с "медленным файлом" 📝, компьютер может переключиться для выполнения других задач.
|
||||
И пока мы ждём завершения работы с «медленныи файлом» 📝, компьютер может заняться другой работой.
|
||||
|
||||
Но при каждой возможности компьютер / программа 🤖 будет возвращаться обратно. Например, если он 🤖 опять окажется в режиме ожидания, или когда закончит всю работу. В этом случае компьютер 🤖 проверяет, не завершена ли какая-нибудь из текущих задач.
|
||||
Затем компьютер/программа 🤖 будет возвращаться каждый раз, когда появится возможность (пока снова где-то идёт ожидание), или когда 🤖 завершит всю текущую работу. И он 🤖 проверит, не завершилась ли какая-либо из задач, которых он ждал, и сделает то, что нужно.
|
||||
|
||||
Потом он 🤖 берёт первую выполненную задачу (допустим, наш "медленный файл" 📝) и продолжает работу, производя с ней необходимые действия.
|
||||
Далее он 🤖 возьмёт первую завершившуюся задачу (скажем, наш «медленный файл» 📝) и продолжит делать с ней то, что требуется.
|
||||
|
||||
Вышеупомянутое "что-то ещё", завершения которого приходится ожидать, обычно относится к достаточно "медленным" операциям <abbr title="Ввода-вывода">I/O</abbr> (по сравнению со скоростью работы процессора и оперативной памяти), например:
|
||||
Это «ожидание чего-то ещё» обычно относится к операциям <abbr title="Input and Output – Ввод/вывод">I/O</abbr>, которые относительно «медленные» (по сравнению со скоростью процессора и оперативной памяти), например ожидание:
|
||||
|
||||
* отправка данных от клиента по сети
|
||||
* получение клиентом данных, отправленных вашей программой по сети
|
||||
* чтение системой содержимого файла с диска и передача этих данных программе
|
||||
* запись на диск данных, которые программа передала системе
|
||||
* обращение к удалённому API
|
||||
* ожидание завершения операции с базой данных
|
||||
* получение результатов запроса к базе данных
|
||||
* и т. д.
|
||||
* отправки данных клиентом по сети
|
||||
* получения клиентом данных, отправленных вашей программой по сети
|
||||
* чтения системой содержимого файла на диске и передачи этих данных вашей программе
|
||||
* записи на диск содержимого, которое ваша программа передала системе
|
||||
* операции удалённого API
|
||||
* завершения операции базы данных
|
||||
* возврата результатов запроса к базе данных
|
||||
* и т.д.
|
||||
|
||||
Поскольку в основном время тратится на ожидание выполнения операций <abbr title="Ввода-вывода">I/O</abbr>,
|
||||
их обычно называют операциями, <abbr title="I/O bound">ограниченными скоростью ввода-вывода</abbr>.
|
||||
Поскольку основное время выполнения уходит на ожидание операций <abbr title="Input and Output – Ввод/вывод">I/O</abbr>, их называют операциями, «ограниченными вводом-выводом» (I/O bound).
|
||||
|
||||
Код называют "асинхронным", потому что компьютеру / программе не требуется "синхронизироваться" с медленной задачей и,
|
||||
будучи в простое, ожидать момента её завершения, с тем чтобы забрать результат и продолжить работу.
|
||||
Это называется «асинхронным», потому что компьютеру/программе не нужно «синхронизироваться» с медленной задачей, простаивая и выжидая точный момент её завершения, чтобы забрать результат и продолжить работу.
|
||||
|
||||
Вместо этого в "асинхронной" системе завершённая задача может немного подождать (буквально несколько микросекунд),
|
||||
пока компьютер / программа занимается другими важными вещами, с тем чтобы потом вернуться,
|
||||
забрать результаты выполнения и начать их обрабатывать.
|
||||
Вместо этого, в «асинхронной» системе, уже завершившаяся задача может немного подождать (несколько микросекунд) в очереди, пока компьютер/программа завершит то, чем занимался, и затем вернётся, чтобы забрать результаты и продолжить работу с ними.
|
||||
|
||||
"Синхронное" исполнение (в противовес "асинхронному") также называют <abbr title="sequential">"последовательным"</abbr>,
|
||||
потому что компьютер / программа последовательно выполняет все требуемые шаги перед тем, как перейти к следующей задаче,
|
||||
даже если в процессе приходится ждать.
|
||||
Для «синхронного» (в противоположность «асинхронному») исполнения часто используют термин «последовательный», потому что компьютер/программа выполняет все шаги по порядку, прежде чем переключиться на другую задачу, даже если эти шаги включают ожидание.
|
||||
|
||||
### Конкурентность и бургеры
|
||||
### Конкурентность и бургеры { #concurrency-and-burgers }
|
||||
|
||||
Тот **асинхронный** код, о котором идёт речь выше, иногда называют **"конкурентностью"**. Она отличается от **"параллелизма"**.
|
||||
Та идея **асинхронного** кода, описанная выше, иногда также называется **«конкурентностью»**. Она отличается от **«параллелизма»**.
|
||||
|
||||
Да, **конкурентность** и **параллелизм** подразумевают, что разные вещи происходят примерно в одно время.
|
||||
И **конкурентность**, и **параллелизм** относятся к «разным вещам, происходящим примерно одновременно».
|
||||
|
||||
Но внутреннее устройство **конкурентности** и **параллелизма** довольно разное.
|
||||
Но различия между *конкурентностью* и *параллелизмом* довольно существенные.
|
||||
|
||||
Чтобы это понять, представьте такую картину:
|
||||
Чтобы их увидеть, представьте следующую историю про бургеры:
|
||||
|
||||
### Конкурентные бургеры
|
||||
### Конкурентные бургеры { #concurrent-burgers }
|
||||
|
||||
<!-- The gender neutral cook emoji "🧑🍳" does not render well in browsers. In the meantime, I'm using a mix of male "👨🍳" and female "👩🍳" cooks. -->
|
||||
Вы идёте со своей возлюбленной за фастфудом, вы стоите в очереди, пока кассир принимает заказы у людей перед вами. 😍
|
||||
|
||||
Вы идёте со своей возлюбленной 😍 в фастфуд 🍔 и становитесь в очередь, в это время кассир 💁 принимает заказы у посетителей перед вами.
|
||||
<img src="/img/async/concurrent-burgers/concurrent-burgers-01.png" class="illustration">
|
||||
|
||||
Когда наконец подходит очередь, вы заказываете парочку самых вкусных и навороченных бургеров 🍔, один для своей возлюбленной 😍, а другой себе.
|
||||
Наконец ваша очередь: вы заказываете 2 очень «навороченных» бургера — для вашей возлюбленной и для себя. 🍔🍔
|
||||
|
||||
Отдаёте деньги 💸.
|
||||
<img src="/img/async/concurrent-burgers/concurrent-burgers-02.png" class="illustration">
|
||||
|
||||
Кассир 💁 что-то говорит поварам на кухне 👨🍳, теперь они знают, какие бургеры нужно будет приготовить 🍔
|
||||
(но пока они заняты бургерами предыдущих клиентов).
|
||||
Кассир говорит что-то повару на кухне, чтобы они знали, что нужно приготовить ваши бургеры (хотя сейчас они готовят бургеры для предыдущих клиентов).
|
||||
|
||||
Кассир 💁 отдаёт вам чек с номером заказа.
|
||||
<img src="/img/async/concurrent-burgers/concurrent-burgers-03.png" class="illustration">
|
||||
|
||||
В ожидании еды вы идёте со своей возлюбленной 😍 выбрать столик, садитесь и довольно продолжительное время общаетесь 😍
|
||||
(поскольку ваши бургеры самые навороченные, готовятся они не так быстро ✨🍔✨).
|
||||
Вы платите. 💸
|
||||
|
||||
Сидя за столиком с возлюбленной 😍 в ожидании бургеров 🍔, вы отлично проводите время,
|
||||
восхищаясь её великолепием, красотой и умом ✨😍✨.
|
||||
Кассир выдаёт вам номер вашей очереди.
|
||||
|
||||
Всё ещё ожидая заказ и болтая со своей возлюбленной 😍, время от времени вы проверяете,
|
||||
какой номер горит над прилавком, и не подошла ли уже ваша очередь.
|
||||
<img src="/img/async/concurrent-burgers/concurrent-burgers-04.png" class="illustration">
|
||||
|
||||
И вот наконец настаёт этот момент, и вы идёте к стойке, чтобы забрать бургеры 🍔 и вернуться за столик.
|
||||
Пока вы ждёте, вы вместе со своей возлюбленной идёте и выбираете столик, садитесь и долго болтаете (ваши бургеры очень «навороченные», поэтому им нужно время на приготовление).
|
||||
|
||||
Вы со своей возлюбленной 😍 едите бургеры 🍔 и отлично проводите время ✨.
|
||||
Сидя за столиком со своей возлюбленной в ожидании бургеров, вы можете провести это время, восхищаясь тем, какая она классная, милая и умная ✨😍✨.
|
||||
|
||||
<img src="/img/async/concurrent-burgers/concurrent-burgers-05.png" class="illustration">
|
||||
|
||||
Пока вы ждёте и разговариваете, время от времени вы поглядываете на номер на табло, чтобы понять, не подошла ли уже ваша очередь.
|
||||
|
||||
И вот в какой-то момент ваша очередь наступает. Вы подходите к стойке, забираете свои бургеры и возвращаетесь к столику.
|
||||
|
||||
<img src="/img/async/concurrent-burgers/concurrent-burgers-06.png" class="illustration">
|
||||
|
||||
Вы со своей возлюбленной едите бургеры и отлично проводите время. ✨
|
||||
|
||||
<img src="/img/async/concurrent-burgers/concurrent-burgers-07.png" class="illustration">
|
||||
|
||||
/// info | Информация
|
||||
|
||||
Прекрасные иллюстрации от <a href="https://www.instagram.com/ketrinadrawsalot" class="external-link" target="_blank">Ketrina Thompson</a>. 🎨
|
||||
|
||||
///
|
||||
|
||||
---
|
||||
|
||||
А теперь представьте, что в этой небольшой истории вы компьютер / программа 🤖.
|
||||
Представьте, что в этой истории вы — компьютер/программа 🤖.
|
||||
|
||||
В очереди вы просто глазеете по сторонам 😴, ждёте и ничего особо "продуктивного" не делаете.
|
||||
Но очередь движется довольно быстро, поскольку кассир 💁 только принимает заказы (а не занимается приготовлением еды), так что ничего страшного.
|
||||
Пока вы стоите в очереди, вы просто бездействуете 😴, ждёте своей очереди и не делаете ничего особо «продуктивного». Но очередь движется быстро, потому что кассир только принимает заказы (а не готовит их), так что это нормально.
|
||||
|
||||
Когда подходит очередь вы наконец предпринимаете "продуктивные" действия 🤓: просматриваете меню, выбираете в нём что-то, узнаёте, что хочет ваша возлюбленная 😍, собираетесь оплатить 💸, смотрите, какую достали карту, проверяете, чтобы с вас списали верную сумму, и что в заказе всё верно и т. д.
|
||||
Когда приходит ваша очередь, вы выполняете действительно «продуктивную» работу: просматриваете меню, решаете, чего хотите, учитываете выбор своей возлюбленной, платите, проверяете, что дали правильную купюру/карту, что сумма списана корректно, что в заказе верные позиции и т.д.
|
||||
|
||||
И хотя вы всё ещё не получили бургеры 🍔, ваша работа с кассиром 💁 ставится "на паузу" ⏸,
|
||||
поскольку теперь нужно ждать 🕙, когда заказ приготовят.
|
||||
Но затем, хотя у вас ещё нет бургеров, ваша «работа» с кассиром поставлена «на паузу» ⏸, потому что нужно подождать 🕙, пока бургеры будут готовы.
|
||||
|
||||
Но отойдя с номерком от прилавка, вы садитесь за столик и можете переключить 🔀 внимание
|
||||
на свою возлюбленную 😍 и "работать" ⏯ 🤓 уже над этим. И вот вы снова очень
|
||||
"продуктивны" 🤓, мило болтаете вдвоём и всё такое 😍.
|
||||
Но, отойдя от стойки и сев за столик с номерком, вы можете переключить 🔀 внимание на свою возлюбленную и «поработать» ⏯ 🤓 над этим. Снова очень «продуктивно» — флирт с вашей возлюбленной 😍.
|
||||
|
||||
В какой-то момент кассир 💁 поместит на табло ваш номер, подразумевая, что бургеры готовы 🍔, но вы не станете подскакивать как умалишённый, лишь только увидев на экране свою очередь. Вы уверены, что ваши бургеры 🍔 никто не утащит, ведь у вас свой номерок, а у других свой.
|
||||
Потом кассир 💁 «говорит»: «Я закончил делать бургеры», — выводя ваш номер на табло, но вы не подпрыгиваете как сумасшедший в ту же секунду, как только номер сменился на ваш. Вы знаете, что ваши бургеры никто не украдёт, потому что у вас есть номер вашей очереди, а у других — их.
|
||||
|
||||
Поэтому вы подождёте, пока возлюбленная 😍 закончит рассказывать историю (закончите текущую работу ⏯ / задачу в обработке 🤓),
|
||||
и мило улыбнувшись, скажете, что идёте забирать заказ ⏸.
|
||||
Поэтому вы дожидаетесь, пока ваша возлюбленная закончит историю (завершится текущая работа ⏯ / выполняемая задача 🤓), мягко улыбаетесь и говорите, что идёте за бургерами ⏸.
|
||||
|
||||
И вот вы подходите к стойке 🔀, к первоначальной задаче, которая уже завершена ⏯, берёте бургеры 🍔, говорите спасибо и относите заказ за столик. На этом заканчивается этап / задача взаимодействия с кассой ⏹.
|
||||
В свою очередь порождается задача "поедание бургеров" 🔀 ⏯, но предыдущая ("получение бургеров") завершена ⏹.
|
||||
Затем вы идёте к стойке 🔀, к исходной задаче, которая теперь завершена ⏯, забираете бургеры, благодарите и несёте их к столику. На этом шаг/задача взаимодействия со стойкой завершён ⏹. Это, в свою очередь, создаёт новую задачу — «есть бургеры» 🔀 ⏯, но предыдущая «получить бургеры» — завершена ⏹.
|
||||
|
||||
### Параллельные бургеры
|
||||
### Параллельные бургеры { #parallel-burgers }
|
||||
|
||||
Теперь представим, что вместо бургерной "Конкурентные бургеры" вы решили сходить в "Параллельные бургеры".
|
||||
Теперь представим, что это не «Конкурентные бургеры», а «Параллельные бургеры».
|
||||
|
||||
И вот вы идёте со своей возлюбленной 😍 отведать параллельного фастфуда 🍔.
|
||||
Вы идёте со своей возлюбленной за параллельным фастфудом.
|
||||
|
||||
Вы становитесь в очередь пока несколько (пусть будет 8) кассиров, которые по совместительству ещё и повары 👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳, принимают заказы у посетителей перед вами.
|
||||
Вы стоите в очереди, пока несколько (скажем, 8) кассиров, которые одновременно являются поварами, принимают заказы у людей перед вами.
|
||||
|
||||
При этом клиенты не отходят от стойки и ждут 🕙 получения еды, поскольку каждый
|
||||
из 8 кассиров идёт на кухню готовить бургеры 🍔, а только потом принимает следующий заказ.
|
||||
Все перед вами ждут, пока их бургеры будут готовы, не отходя от стойки, потому что каждый из 8 кассиров сразу идёт готовить бургер перед тем, как принять следующий заказ.
|
||||
|
||||
Наконец настаёт ваша очередь, и вы просите два самых навороченных бургера 🍔, один для дамы сердца 😍, а другой себе.
|
||||
<img src="/img/async/parallel-burgers/parallel-burgers-01.png" class="illustration">
|
||||
|
||||
Ни о чём не жалея, расплачиваетесь 💸.
|
||||
Наконец ваша очередь: вы заказываете 2 очень «навороченных» бургера — для вашей возлюбленной и для себя.
|
||||
|
||||
И кассир уходит на кухню 👨🍳.
|
||||
Вы платите 💸.
|
||||
|
||||
Вам приходится ждать перед стойкой 🕙, чтобы никто по случайности не забрал ваши бургеры 🍔, ведь никаких номерков у вас нет.
|
||||
<img src="/img/async/parallel-burgers/parallel-burgers-02.png" class="illustration">
|
||||
|
||||
Поскольку вы с возлюбленной 😍 хотите получить заказ вовремя 🕙, и следите за тем, чтобы никто не вклинился в очередь,
|
||||
у вас не получается уделять должного внимание своей даме сердца 😞.
|
||||
Кассир уходит на кухню.
|
||||
|
||||
Это "синхронная" работа, вы "синхронизированы" с кассиром/поваром 👨🍳. Приходится ждать 🕙 у стойки,
|
||||
когда кассир/повар 👨🍳 закончит делать бургеры 🍔 и вручит вам заказ, иначе его случайно может забрать кто-то другой.
|
||||
Вы ждёте, стоя у стойки 🕙, чтобы никто не забрал ваши бургеры раньше вас, так как никаких номерков нет.
|
||||
|
||||
Наконец кассир/повар 👨🍳 возвращается с бургерами 🍔 после невыносимо долгого ожидания 🕙 за стойкой.
|
||||
<img src="/img/async/parallel-burgers/parallel-burgers-03.png" class="illustration">
|
||||
|
||||
Вы скорее забираете заказ 🍔 и идёте с возлюбленной 😍 за столик.
|
||||
Так как вы со своей возлюбленной заняты тем, чтобы никто не встал перед вами и не забрал ваши бургеры, как только они появятся, вы не можете уделить внимание своей возлюбленной. 😞
|
||||
|
||||
Там вы просто едите эти бургеры, и на этом всё 🍔 ⏹.
|
||||
Это «синхронная» работа, вы «синхронизированы» с кассиром/поваром 👨🍳. Вам нужно ждать 🕙 и находиться там в точный момент, когда кассир/повар 👨🍳 закончит бургеры и вручит их вам, иначе их может забрать кто-то другой.
|
||||
|
||||
Вам не особо удалось пообщаться, потому что большую часть времени 🕙 пришлось провести у кассы 😞.
|
||||
<img src="/img/async/parallel-burgers/parallel-burgers-04.png" class="illustration">
|
||||
|
||||
Затем ваш кассир/повар 👨🍳 наконец возвращается с вашими бургерами, после долгого ожидания 🕙 у стойки.
|
||||
|
||||
<img src="/img/async/parallel-burgers/parallel-burgers-05.png" class="illustration">
|
||||
|
||||
Вы берёте бургеры и идёте со своей возлюбленной к столику.
|
||||
|
||||
Вы просто их съедаете — и всё. ⏹
|
||||
|
||||
<img src="/img/async/parallel-burgers/parallel-burgers-06.png" class="illustration">
|
||||
|
||||
Разговоров и флирта было немного, потому что большую часть времени вы ждали 🕙 у стойки. 😞
|
||||
|
||||
/// info | Информация
|
||||
|
||||
Прекрасные иллюстрации от <a href="https://www.instagram.com/ketrinadrawsalot" class="external-link" target="_blank">Ketrina Thompson</a>. 🎨
|
||||
|
||||
///
|
||||
|
||||
---
|
||||
|
||||
В описанном сценарии вы компьютер / программа 🤖 с двумя исполнителями (вы и ваша возлюбленная 😍),
|
||||
на протяжении долгого времени 🕙 вы оба уделяете всё внимание ⏯ задаче "ждать на кассе".
|
||||
В этом сценарии «параллельных бургеров» вы — компьютер/программа 🤖 с двумя процессорами (вы и ваша возлюбленная), оба ждут 🕙 и уделяют внимание ⏯ тому, чтобы «ждать у стойки» 🕙 долгое время.
|
||||
|
||||
В этом ресторане быстрого питания 8 исполнителей (кассиров/поваров) 👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳.
|
||||
Хотя в бургерной конкурентного типа было всего два (один кассир и один повар) 💁 👨🍳.
|
||||
В ресторане 8 процессоров (кассиров/поваров). Тогда как в «конкурентных бургерах» могло быть только 2 (один кассир и один повар).
|
||||
|
||||
Несмотря на обилие работников, опыт в итоге получился не из лучших 😞.
|
||||
И всё же финальный опыт — не самый лучший. 😞
|
||||
|
||||
---
|
||||
|
||||
Так бы выглядел аналог истории про бургерную 🍔 в "параллельном" мире.
|
||||
Это была параллельная версия истории про бургеры. 🍔
|
||||
|
||||
Вот более реалистичный пример. Представьте себе банк.
|
||||
Для более «жизненного» примера представьте банк.
|
||||
|
||||
До недавних пор в большинстве банков было несколько кассиров 👨💼👨💼👨💼👨💼 и длинные очереди 🕙🕙🕙🕙🕙🕙🕙🕙.
|
||||
До недавнего времени в большинстве банков было несколько кассиров 👨💼👨💼👨💼👨💼 и длинная очередь 🕙🕙🕙🕙🕙🕙🕙🕙.
|
||||
|
||||
Каждый кассир обслуживал одного клиента, потом следующего 👨💼⏯.
|
||||
Все кассиры делают всю работу с одним клиентом за другим 👨💼⏯.
|
||||
|
||||
Нужно было долгое время 🕙 стоять перед окошком вместе со всеми, иначе пропустишь свою очередь.
|
||||
И вам приходится долго 🕙 стоять в очереди, иначе вы потеряете свою очередь.
|
||||
|
||||
Сомневаюсь, что у вас бы возникло желание прийти с возлюбленной 😍 в банк 🏦 оплачивать налоги.
|
||||
Вы вряд ли захотите взять свою возлюбленную 😍 с собой, чтобы заняться делами в банке 🏦.
|
||||
|
||||
### Выводы о бургерах
|
||||
### Вывод про бургеры { #burger-conclusion }
|
||||
|
||||
В нашей истории про поход в фастфуд за бургерами приходится много ждать 🕙,
|
||||
поэтому имеет смысл организовать конкурентную систему ⏸🔀⏯.
|
||||
В этом сценарии «фастфуда с вашей возлюбленной», так как много ожидания 🕙, гораздо логичнее иметь конкурентную систему ⏸🔀⏯.
|
||||
|
||||
И то же самое с большинством веб-приложений.
|
||||
Так обстоит дело и с большинством веб-приложений.
|
||||
|
||||
Пользователей очень много, но ваш сервер всё равно вынужден ждать 🕙 запросы по их слабому интернет-соединению.
|
||||
Очень много пользователей, но ваш сервер ждёт 🕙, пока их не самое хорошее соединение отправит их запросы.
|
||||
|
||||
Потом снова ждать 🕙, пока вернётся ответ.
|
||||
А затем снова ждёт 🕙, пока отправятся ответы.
|
||||
|
||||
<!--https://forum.wordreference.com/threads/%D0%9D%D0%BE-%D0%B5%D1%81%D0%BB%D0%B8.3258695/-->
|
||||
Это ожидание 🕙 измеряется микросекундами, но если всё сложить, то набегает довольно много времени.
|
||||
Это «ожидание» 🕙 измеряется микросекундами, но если всё сложить, то в сумме получается много ожидания.
|
||||
|
||||
Вот почему есть смысл использовать асинхронное ⏸🔀⏯ программирование при построении веб-API.
|
||||
Вот почему асинхронный ⏸🔀⏯ код очень уместен для веб-API.
|
||||
|
||||
Большинство популярных фреймворков (включая Flask и Django) создавались
|
||||
до появления в Python новых возможностей асинхронного программирования. Поэтому
|
||||
их можно разворачивать с поддержкой параллельного исполнения или асинхронного
|
||||
программирования старого типа, которое не настолько эффективно.
|
||||
Именно такая асинхронность сделала NodeJS популярным (хотя NodeJS — не параллельный), и это сильная сторона Go как языка программирования.
|
||||
|
||||
При том, что основная спецификация асинхронного взаимодействия Python с веб-сервером
|
||||
(<a href="https://asgi.readthedocs.io" class="external-link" target="_blank">ASGI</a>)
|
||||
была разработана командой Django для внедрения поддержки веб-сокетов.
|
||||
Того же уровня производительности вы получаете с **FastAPI**.
|
||||
|
||||
Именно асинхронность сделала NodeJS таким популярным (несмотря на то, что он не параллельный),
|
||||
и в этом преимущество Go как языка программирования.
|
||||
А так как можно одновременно использовать параллелизм и асинхронность, вы получаете производительность выше, чем у большинства протестированных фреймворков на NodeJS и на уровне Go, который — компилируемый язык, ближе к C <a href="https://www.techempower.com/benchmarks/#section=data-r17&hw=ph&test=query&l=zijmkf-1" class="external-link" target="_blank">(всё благодаря Starlette)</a>.
|
||||
|
||||
И тот же уровень производительности даёт **FastAPI**.
|
||||
### Конкурентность лучше параллелизма? { #is-concurrency-better-than-parallelism }
|
||||
|
||||
Поскольку можно использовать преимущества параллелизма и асинхронности вместе,
|
||||
вы получаете производительность лучше, чем у большинства протестированных NodeJS фреймворков
|
||||
и на уровне с Go, который является компилируемым языком близким к C <a href="https://www.techempower.com/benchmarks/#section=data-r17&hw=ph&test=query&l=zijmkf-1" class="external-link" target="_blank">(всё благодаря Starlette)</a>.
|
||||
Нет! Мораль истории не в этом.
|
||||
|
||||
### Получается, конкурентность лучше параллелизма?
|
||||
Конкурентность отличается от параллелизма. И она лучше в **конкретных** сценариях, где много ожидания. Поэтому при разработке веб-приложений она обычно намного лучше параллелизма. Но не во всём.
|
||||
|
||||
Нет! Мораль истории совсем не в этом.
|
||||
Чтобы уравновесить это, представьте такую короткую историю:
|
||||
|
||||
Конкурентность отличается от параллелизма. Она лучше в **конкретных** случаях, где много времени приходится на ожидание.
|
||||
Вот почему она зачастую лучше параллелизма при разработке веб-приложений. Но это не значит, что конкурентность лучше в любых сценариях.
|
||||
|
||||
Давайте посмотрим с другой стороны, представьте такую картину:
|
||||
|
||||
> Вам нужно убраться в большом грязном доме.
|
||||
> Вам нужно убрать большой грязный дом.
|
||||
|
||||
*Да, это вся история*.
|
||||
|
||||
---
|
||||
|
||||
Тут не нужно нигде ждать 🕙, просто есть куча работы в разных частях дома.
|
||||
Здесь нигде нет ожидания 🕙, просто очень много работы в разных местах дома.
|
||||
|
||||
Можно организовать очередь как в примере с бургерами, сначала гостиная, потом кухня,
|
||||
но это ни на что не повлияет, поскольку вы нигде не ждёте 🕙, а просто трёте да моете.
|
||||
Можно организовать «очереди» как в примере с бургерами — сначала гостиная, потом кухня, — но так как вы ничего не ждёте 🕙, а просто убираете и убираете, очереди ни на что не повлияют.
|
||||
|
||||
И понадобится одинаковое количество времени с очередью (конкурентностью) и без неё,
|
||||
и работы будет сделано тоже одинаковое количество.
|
||||
На завершение уйдёт одинаковое время — с очередями (конкурентностью) и без них — и объём выполненной работы будет одинаковым.
|
||||
|
||||
Однако в случае, если бы вы могли привести 8 бывших кассиров/поваров, а ныне уборщиков 👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳,
|
||||
и каждый из них (вместе с вами) взялся бы за свой участок дома,
|
||||
с такой помощью вы бы закончили намного быстрее, делая всю работу **параллельно**.
|
||||
Но в этом случае, если бы вы могли привести 8 бывших кассиров/поваров, а теперь — уборщиков, и каждый из них (плюс вы) взял бы свою зону дома для уборки, вы могли бы сделать всю работу **параллельно**, с дополнительной помощью, и завершить гораздо быстрее.
|
||||
|
||||
В описанном сценарии каждый уборщик (включая вас) был бы исполнителем, занятым на своём участке работы.
|
||||
В этом сценарии каждый уборщик (включая вас) был бы процессором, выполняющим свою часть работы.
|
||||
|
||||
И поскольку большую часть времени выполнения занимает реальная работа (а не ожидание),
|
||||
а работу в компьютере делает <abbr title="Центральный процессор (CPU)">ЦП</abbr>,
|
||||
такие задачи называют <abbr title="CPU bound">ограниченными производительностью процессора</abbr>.
|
||||
И так как основное время выполнения уходит на реальную работу (а не ожидание), а работу в компьютере выполняет <abbr title="Central Processing Unit – Центральный процессор">CPU</abbr>, такие задачи называют «ограниченными процессором» (CPU bound).
|
||||
|
||||
---
|
||||
|
||||
Ограничение по процессору проявляется в операциях, где требуется выполнять сложные математические вычисления.
|
||||
Типичные примеры CPU-bound операций — те, которые требуют сложной математической обработки.
|
||||
|
||||
Например:
|
||||
|
||||
* Обработка **звука** или **изображений**.
|
||||
* **Компьютерное зрение**: изображение состоит из миллионов пикселей, в каждом пикселе 3 составляющих цвета,
|
||||
обработка обычно требует проведения расчётов по всем пикселям сразу.
|
||||
* **Машинное обучение**: здесь обычно требуется умножение "матриц" и "векторов".
|
||||
Представьте гигантскую таблицу с числами в Экселе, и все их надо одновременно перемножить.
|
||||
* **Глубокое обучение**: это область *машинного обучения*, поэтому сюда подходит то же описание.
|
||||
Просто у вас будет не одна таблица в Экселе, а множество. В ряде случаев используется
|
||||
специальный процессор для создания и / или использования построенных таким образом моделей.
|
||||
* Обработка **аудио** или **изображений**.
|
||||
* **Компьютерное зрение**: изображение состоит из миллионов пикселей, каждый пиксель имеет 3 значения/цвета; обычно требуется вычислить что-то для всех этих пикселей одновременно.
|
||||
* **Машинное обучение**: обычно требует множества умножений «матриц» и «векторов». Представьте огромную таблицу с числами и умножение всех этих чисел «одновременно».
|
||||
* **Глубокое обучение**: это подполе Машинного обучения, так что всё вышесказанное применимо. Просто это не одна таблица чисел, а их огромный набор, и во многих случаях вы используете специальный процессор, чтобы строить и/или использовать такие модели.
|
||||
|
||||
### Конкурентность + параллелизм: Веб + машинное обучение
|
||||
### Конкурентность + параллелизм: Веб + Машинное обучение { #concurrency-parallelism-web-machine-learning }
|
||||
|
||||
**FastAPI** предоставляет возможности конкуретного программирования,
|
||||
которое очень распространено в веб-разработке (именно этим славится NodeJS).
|
||||
С **FastAPI** вы можете использовать преимущества конкурентности, что очень распространено в веб-разработке (это та же основная «фишка» NodeJS).
|
||||
|
||||
Кроме того вы сможете использовать все преимущества параллелизма и
|
||||
<abbr title="multiprocessing">многопроцессорности</abbr> (когда несколько процессов работают параллельно),
|
||||
если рабочая нагрузка предполагает **ограничение по процессору**,
|
||||
как, например, в системах машинного обучения. <!--http://new.gramota.ru/spravka/punctum?layout=item&id=58_329-->
|
||||
Но вы также можете использовать выгоды параллелизма и многопроцессности (когда несколько процессов работают параллельно) для рабочих нагрузок, **ограниченных процессором** (CPU bound), как в системах Машинного обучения.
|
||||
|
||||
Необходимо также отметить, что Python является главным языком в области
|
||||
<abbr title="наука о данных (data science)">**дата-сайенс**</abbr>,
|
||||
машинного обучения и, особенно, глубокого обучения. Всё это делает FastAPI
|
||||
отличным вариантом (среди многих других) для разработки веб-API и приложений
|
||||
в области дата-сайенс / машинного обучения.
|
||||
Плюс к этому простой факт, что Python — основной язык для **Data Science**, Машинного обучения и особенно Глубокого обучения, делает FastAPI очень хорошим выбором для веб-API и приложений в области Data Science / Машинного обучения (среди многих других).
|
||||
|
||||
Как добиться такого параллелизма в эксплуатации описано в разделе [Развёртывание](deployment/index.md){.internal-link target=_blank}.
|
||||
Как добиться такого параллелизма в продакшн, см. раздел [Развёртывание](deployment/index.md){.internal-link target=_blank}.
|
||||
|
||||
## `async` и `await`
|
||||
## `async` и `await` { #async-and-await }
|
||||
|
||||
В современных версиях Python разработка асинхронного кода реализована очень интуитивно.
|
||||
Он выглядит как обычный "последовательный" код и самостоятельно выполняет "ожидание", когда это необходимо.
|
||||
В современных версиях Python есть очень интуитивный способ определять асинхронный код. Это делает его похожим на обычный «последовательный» код, а «ожидание» выполняется за вас в нужные моменты.
|
||||
|
||||
Если некая операция требует ожидания перед тем, как вернуть результат, и
|
||||
поддерживает современные возможности Python, код можно написать следующим образом:
|
||||
Когда есть операция, которой нужно подождать перед тем, как вернуть результат, и она поддерживает эти новые возможности Python, вы можете написать так:
|
||||
|
||||
```Python
|
||||
burgers = await get_burgers(2)
|
||||
```
|
||||
|
||||
Главное здесь слово `await`. Оно сообщает интерпретатору, что необходимо дождаться ⏸
|
||||
пока `get_burgers(2)` закончит свои дела 🕙, и только после этого сохранить результат в `burgers`.
|
||||
Зная это, Python может пока переключиться на выполнение других задач 🔀 ⏯
|
||||
(например получение следующего запроса).<!--http://new.gramota.ru/spravka/buro/search-answer?s=296614-->
|
||||
Ключ здесь — `await`. Он говорит Python, что нужно подождать ⏸, пока `get_burgers(2)` закончит своё дело 🕙, прежде чем сохранять результат в `burgers`. Благодаря этому Python будет знать, что за это время можно заняться чем-то ещё 🔀 ⏯ (например, принять другой запрос).
|
||||
|
||||
Чтобы ключевое слово `await` сработало, оно должно находиться внутри функции,
|
||||
которая поддерживает асинхронность. Для этого вам просто нужно объявить её как `async def`:
|
||||
Чтобы `await` работал, он должен находиться внутри функции, которая поддерживает такую асинхронность. Для этого просто объявите её с `async def`:
|
||||
|
||||
```Python hl_lines="1"
|
||||
async def get_burgers(number: int):
|
||||
# Готовим бургеры по специальному асинхронному рецепту
|
||||
# Сделать что-то асинхронное, чтобы приготовить бургеры
|
||||
return burgers
|
||||
```
|
||||
|
||||
@@ -355,26 +325,22 @@ async def get_burgers(number: int):
|
||||
```Python hl_lines="2"
|
||||
# Это не асинхронный код
|
||||
def get_sequential_burgers(number: int):
|
||||
# Готовим бургеры последовательно по шагам
|
||||
# Сделать что-то последовательное, чтобы приготовить бургеры
|
||||
return burgers
|
||||
```
|
||||
|
||||
Объявление `async def` указывает интерпретатору, что внутри этой функции
|
||||
следует ожидать выражений `await`, и что можно поставить выполнение такой функции на "паузу" ⏸ и
|
||||
переключиться на другие задачи 🔀, с тем чтобы вернуться сюда позже.
|
||||
С `async def` Python знает, что внутри этой функции нужно учитывать выражения `await` и что выполнение такой функции можно «приостанавливать» ⏸ и идти делать что-то ещё 🔀, чтобы потом вернуться.
|
||||
|
||||
Если вы хотите вызвать функцию с `async def`, вам нужно <abbr title="await">"ожидать"</abbr> её.
|
||||
Поэтому такое не сработает:
|
||||
Когда вы хотите вызвать функцию, объявленную с `async def`, нужно её «ожидать». Поэтому вот так не сработает:
|
||||
|
||||
```Python
|
||||
# Это не заработает, поскольку get_burgers объявлена с использованием async def
|
||||
# Это не сработает, потому что get_burgers определена с: async def
|
||||
burgers = get_burgers(2)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Если сторонняя библиотека требует вызывать её с ключевым словом `await`,
|
||||
необходимо писать *функции обработки пути* с использованием `async def`, например:
|
||||
Итак, если вы используете библиотеку, которую можно вызывать с `await`, вам нужно создать *функцию-обработчик пути*, которая её использует, с `async def`, например:
|
||||
|
||||
```Python hl_lines="2-3"
|
||||
@app.get('/burgers')
|
||||
@@ -383,129 +349,96 @@ async def read_burgers():
|
||||
return burgers
|
||||
```
|
||||
|
||||
### Технические подробности
|
||||
### Более технические подробности { #more-technical-details }
|
||||
|
||||
Как вы могли заметить, `await` может применяться только в функциях, объявленных с использованием `async def`.
|
||||
Вы могли заметить, что `await` можно использовать только внутри функций, определённых с `async def`.
|
||||
|
||||
<!--http://new.gramota.ru/spravka/punctum?layout=item&id=58_128-->
|
||||
Но выполнение такой функции необходимо "ожидать" с помощью `await`.
|
||||
Это означает, что её можно вызвать только из другой функции, которая тоже объявлена с `async def`.
|
||||
Но при этом функции, определённые с `async def`, нужно «ожидать». Значит, функции с `async def` тоже можно вызывать только из функций, определённых с `async def`.
|
||||
|
||||
Но как же тогда появилась первая <abbr title="или яйцо?🤔">курица</abbr>? В смысле... как нам вызвать первую асинхронную функцию?
|
||||
Так что же с «яйцом и курицей» — как вызвать первую `async` функцию?
|
||||
|
||||
При работе с **FastAPI** просто не думайте об этом, потому что "первой" функцией является ваша *функция обработки пути*,
|
||||
и дальше с этим разберётся FastAPI.
|
||||
Если вы работаете с **FastAPI**, вам не о чем беспокоиться, потому что этой «первой» функцией будет ваша *функция-обработчик пути*, а FastAPI знает, как сделать всё правильно.
|
||||
|
||||
Кроме того, если хотите, вы можете использовать синтаксис `async` / `await` и без FastAPI.
|
||||
Но если вы хотите использовать `async` / `await` без FastAPI, вы тоже можете это сделать.
|
||||
|
||||
### Пишите свой асинхронный код
|
||||
### Пишите свой асинхронный код { #write-your-own-async-code }
|
||||
|
||||
Starlette (и **FastAPI**) основаны на <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a>, что делает их совместимыми как со стандартной библиотекой <a href="https://docs.python.org/3/library/asyncio-task.html" class="external-link" target="_blank">asyncio</a> в Python, так и с <a href="https://trio.readthedocs.io/en/stable/" class="external-link" target="_blank">Trio</a>.<!--http://new.gramota.ru/spravka/buro/search-answer?s=285295-->
|
||||
Starlette (и **FastAPI**) основаны на <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a>, что делает их совместимыми и со стандартной библиотекой Python <a href="https://docs.python.org/3/library/asyncio-task.html" class="external-link" target="_blank">asyncio</a>, и с <a href="https://trio.readthedocs.io/en/stable/" class="external-link" target="_blank">Trio</a>.
|
||||
|
||||
В частности, вы можете напрямую использовать <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a> в тех проектах, где требуется более сложная логика работы с конкурентностью.
|
||||
В частности, вы можете напрямую использовать <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a> для продвинутых сценариев конкурентности, где в вашем коде нужны более сложные паттерны.
|
||||
|
||||
Даже если вы не используете FastAPI, вы можете писать асинхронные приложения с помощью <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a>, чтобы они были максимально совместимыми и получали его преимущества (например *структурную конкурентность*).
|
||||
И даже если вы не используете FastAPI, вы можете писать свои асинхронные приложения с <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a>, чтобы они были максимально совместимыми и получали его преимущества (например, *структурную конкурентность*).
|
||||
|
||||
### Другие виды асинхронного программирования
|
||||
Я создал ещё одну библиотеку поверх AnyIO, тонкий слой, чтобы немного улучшить аннотации типов и получить более качественное **автозавершение**, **ошибки прямо в редакторе** и т.д. Там также есть дружелюбное введение и руководство, чтобы помочь вам **понять** и писать **свой собственный асинхронный код**: <a href="https://asyncer.tiangolo.com/" class="external-link" target="_blank">Asyncer</a>. Она особенно полезна, если вам нужно **комбинировать асинхронный код с обычным** (блокирующим/синхронным) кодом.
|
||||
|
||||
Стиль написания кода с `async` и `await` появился в языке Python относительно недавно.
|
||||
### Другие формы асинхронного кода { #other-forms-of-asynchronous-code }
|
||||
|
||||
Но он сильно облегчает работу с асинхронным кодом.
|
||||
Такой стиль использования `async` и `await` относительно новый в языке.
|
||||
|
||||
Ровно такой же синтаксис (ну или почти такой же) недавно был включён в современные версии JavaScript (в браузере и NodeJS).
|
||||
Но он сильно упрощает работу с асинхронным кодом.
|
||||
|
||||
До этого поддержка асинхронного кода была реализована намного сложнее, и его было труднее воспринимать.
|
||||
Такой же (или почти такой же) синтаксис недавно появился в современных версиях JavaScript (в браузере и NodeJS).
|
||||
|
||||
В предыдущих версиях Python для этого использовались потоки или <a href="https://www.gevent.org/" class="external-link" target="_blank">Gevent</a>. Но такой код намного сложнее понимать, отлаживать и мысленно представлять.
|
||||
До этого работа с асинхронным кодом была заметно сложнее и труднее для понимания.
|
||||
|
||||
Что касается JavaScript (в браузере и NodeJS), раньше там использовали для этой цели
|
||||
<abbr title="callback">"обратные вызовы"</abbr>. Что выливалось в
|
||||
"ад обратных вызовов".
|
||||
В предыдущих версиях Python можно было использовать потоки или <a href="https://www.gevent.org/" class="external-link" target="_blank">Gevent</a>. Но такой код гораздо сложнее понимать, отлаживать и держать в голове.
|
||||
|
||||
## Сопрограммы
|
||||
В прежних версиях NodeJS/браузерного JavaScript вы бы использовали «callbacks» (обратные вызовы), что приводит к «callback hell» (ад обратных вызовов).
|
||||
|
||||
<abbr title="coroutine">**Корути́на**</abbr> (или же сопрограмма) — это крутое словечко для именования той сущности,
|
||||
которую возвращает функция `async def`. Python знает, что её можно запустить, как и обычную функцию,
|
||||
но кроме того сопрограмму можно поставить на паузу ⏸ в том месте, где встретится слово `await`.
|
||||
## Сопрограммы { #coroutines }
|
||||
|
||||
Всю функциональность асинхронного программирования с использованием `async` и `await`
|
||||
часто обобщают словом "корутины". Они аналогичны <abbr title="Goroutines">"горутинам"</abbr>, ключевой особенности
|
||||
языка Go.
|
||||
**Сопрограмма** (coroutine) — это просто «навороченное» слово для того, что возвращает функция `async def`. Python знает, что это похоже на функцию: её можно запустить, она когда-нибудь завершится, но её выполнение может приостанавливаться ⏸ внутри, когда встречается `await`.
|
||||
|
||||
## Заключение
|
||||
Часто всю функциональность использования асинхронного кода с `async` и `await` кратко называют «сопрограммами». Это сопоставимо с ключевой особенностью Go — «goroutines».
|
||||
|
||||
В самом начале была такая фраза:
|
||||
## Заключение { #conclusion }
|
||||
|
||||
> Современные версии Python поддерживают разработку так называемого
|
||||
**"асинхронного кода"** посредством написания **"сопрограмм"** с использованием
|
||||
синтаксиса **`async` и `await`**.
|
||||
Вернёмся к той же фразе:
|
||||
|
||||
Теперь всё должно звучать понятнее. ✨
|
||||
> Современные версии Python поддерживают **«асинхронный код»** с помощью **«сопрограмм»** (coroutines) и синтаксиса **`async` и `await`**.
|
||||
|
||||
На этом основана работа FastAPI (посредством Starlette), и именно это
|
||||
обеспечивает его высокую производительность.
|
||||
Теперь это должно звучать понятнее. ✨
|
||||
|
||||
## Очень технические подробности
|
||||
Именно это «движет» FastAPI (через Starlette) и обеспечивает столь впечатляющую производительность.
|
||||
|
||||
/// warning
|
||||
## Очень технические подробности { #very-technical-details }
|
||||
|
||||
Этот раздел читать не обязательно.
|
||||
/// warning | Предупреждение
|
||||
|
||||
Здесь приводятся подробности внутреннего устройства **FastAPI**.
|
||||
Скорее всего, этот раздел можно пропустить.
|
||||
|
||||
Но если вы обладаете техническими знаниями (корутины, потоки, блокировка и т. д.)
|
||||
и вам интересно, как FastAPI обрабатывает `async def` в отличие от обычных `def`,
|
||||
читайте дальше.
|
||||
Здесь — очень технические подробности о том, как **FastAPI** работает «под капотом».
|
||||
|
||||
Если у вас есть достаточно технических знаний (сопрограммы, потоки, блокировки и т.д.) и вам интересно, как FastAPI обрабатывает `async def` по сравнению с обычным `def`, — вперёд.
|
||||
|
||||
///
|
||||
|
||||
### Функции обработки пути
|
||||
### Функции-обработчики пути { #path-operation-functions }
|
||||
|
||||
Когда вы объявляете *функцию обработки пути* обычным образом с ключевым словом `def`
|
||||
вместо `async def`, FastAPI ожидает её выполнения, запустив функцию во внешнем
|
||||
<abbr title="threadpool">пуле потоков</abbr>, а не напрямую (это бы заблокировало сервер).
|
||||
Когда вы объявляете *функцию-обработчик пути* обычным `def` вместо `async def`, она запускается во внешнем пуле потоков, который затем «ожидается», вместо прямого вызова (прямой вызов заблокировал бы сервер).
|
||||
|
||||
Если ранее вы использовали другой асинхронный фреймворк, который работает иначе,
|
||||
и привыкли объявлять простые вычислительные *функции* через `def` ради
|
||||
незначительного прироста скорости (порядка 100 наносекунд), обратите внимание,
|
||||
что с **FastAPI** вы получите противоположный эффект. В таком случае больше подходит
|
||||
`async def`, если только *функция обработки пути* не использует код, приводящий
|
||||
к блокировке <abbr title="Ввод/вывод: чтение и запись на диск, сетевые соединения.">I/O</abbr>.
|
||||
<!--Уточнить: Не использовать async def, если код приводит к блокировке IO?-->
|
||||
Если вы пришли из другого async-фреймворка, который работает иначе, и привыкли объявлять тривиальные *функции-обработчики пути*, выполняющие только вычисления, через простой `def` ради крошечной выгоды в производительности (около 100 наносекунд), обратите внимание: в **FastAPI** эффект будет противоположным. В таких случаях лучше использовать `async def`, если только ваши *функции-обработчики пути* не используют код, выполняющий блокирующий <abbr title="Input/Output – Ввод/вывод: чтение или запись на диск, сетевые соединения.">I/O</abbr>.
|
||||
|
||||
<!--http://new.gramota.ru/spravka/punctum?layout=item&id=58_285-->
|
||||
Но в любом случае велика вероятность, что **FastAPI** [окажется быстрее](index.md#_11){.internal-link target=_blank}
|
||||
другого фреймворка (или хотя бы на уровне с ним).
|
||||
Тем не менее, в обоих случаях велика вероятность, что **FastAPI** [всё равно будет быстрее](index.md#performance){.internal-link target=_blank} (или как минимум сопоставим) с вашим предыдущим фреймворком.
|
||||
|
||||
### Зависимости
|
||||
### Зависимости { #dependencies }
|
||||
|
||||
То же относится к зависимостям. Если это обычная функция `def`, а не `async def`,
|
||||
она запускается во внешнем пуле потоков.
|
||||
То же относится к [зависимостям](tutorial/dependencies/index.md){.internal-link target=_blank}. Если зависимость — это обычная функция `def`, а не `async def`, она запускается во внешнем пуле потоков.
|
||||
|
||||
### Подзависимости
|
||||
### Подзависимости { #sub-dependencies }
|
||||
|
||||
Вы можете объявить множество ссылающихся друг на друга зависимостей и подзависимостей
|
||||
(в виде параметров при определении функции). Какие-то будут созданы с помощью `async def`,
|
||||
другие обычным образом через `def`, и такая схема вполне работоспособна. Функции,
|
||||
объявленные с помощью `def` будут запускаться на внешнем потоке (из пула),
|
||||
а не с помощью `await`.
|
||||
У вас может быть несколько зависимостей и [подзависимостей](tutorial/dependencies/sub-dependencies.md){.internal-link target=_blank}, которые требуют друг друга (в виде параметров определений функций): часть из них может быть объявлена с `async def`, а часть — обычным `def`. Всё будет работать, а те, что объявлены обычным `def`, будут вызываться во внешнем потоке (из пула), а не «ожидаться».
|
||||
|
||||
### Другие служебные функции
|
||||
### Другие служебные функции { #other-utility-functions }
|
||||
|
||||
Любые другие служебные функции, которые вы вызываете напрямую, можно объявлять
|
||||
с использованием `def` или `async def`. FastAPI не будет влиять на то, как вы
|
||||
их запускаете.
|
||||
Любые другие служебные функции, которые вы вызываете напрямую, можно объявлять обычным `def` или `async def`, и FastAPI не будет влиять на то, как вы их вызываете.
|
||||
|
||||
Этим они отличаются от функций, которые FastAPI вызывает самостоятельно:
|
||||
*функции обработки пути* и зависимости.
|
||||
В отличие от функций, которые FastAPI вызывает за вас: *функции-обработчики пути* и зависимости.
|
||||
|
||||
Если служебная функция объявлена с помощью `def`, она будет вызвана напрямую
|
||||
(как вы и написали в коде), а не в отдельном потоке. Если же она объявлена с
|
||||
помощью `async def`, её вызов должен осуществляться с ожиданием через `await`.
|
||||
Если служебная функция — обычная функция с `def`, она будет вызвана напрямую (как вы и пишете в коде), не в пуле потоков; если функция объявлена с `async def`, тогда при её вызове в вашем коде вы должны использовать `await`.
|
||||
|
||||
---
|
||||
|
||||
<!--http://new.gramota.ru/spravka/buro/search-answer?s=299749-->
|
||||
Ещё раз повторим, что все эти технические подробности полезны, только если вы специально их искали.
|
||||
Снова: это очень технические подробности, полезные, вероятно, только если вы целенаправленно их ищете.
|
||||
|
||||
В противном случае просто ознакомьтесь с основными принципами в разделе выше: <a href="#_1">Нет времени?</a>.
|
||||
Иначе вам достаточно руководствоваться рекомендациями из раздела выше: <a href="#in-a-hurry">Нет времени?</a>.
|
||||
|
||||
@@ -1,37 +1,34 @@
|
||||
# Замеры производительности
|
||||
# Бенчмарки (тесты производительности) { #benchmarks }
|
||||
|
||||
Независимые тесты производительности приложений от TechEmpower показывают, что **FastAPI** под управлением Uvicorn <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" class="external-link" target="_blank">один из самых быстрых Python-фреймворков</a> и уступает только Starlette и Uvicorn (которые используются в FastAPI). (*)
|
||||
Независимые бенчмарки TechEmpower показывают, что приложения **FastAPI** под управлением Uvicorn — <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" class="external-link" target="_blank">одни из самых быстрых Python‑фреймворков</a>, уступающие только Starlette и самому Uvicorn (используются внутри FastAPI).
|
||||
|
||||
Но при просмотре и сравнении замеров производительности следует иметь в виду нижеописанное.
|
||||
Но при просмотре бенчмарков и сравнений следует иметь в виду следующее.
|
||||
|
||||
## Замеры производительности и скорости
|
||||
## Бенчмарки и скорость { #benchmarks-and-speed }
|
||||
|
||||
В подобных тестах часто можно увидеть, что инструменты разного типа сравнивают друг с другом, как аналогичные.
|
||||
При проверке бенчмарков часто можно увидеть, что инструменты разных типов сравнивают как эквивалентные.
|
||||
|
||||
В частности, сравнивают вместе Uvicorn, Starlette и FastAPI (среди многих других инструментов).
|
||||
В частности, часто сравнивают вместе Uvicorn, Starlette и FastAPI (среди многих других инструментов).
|
||||
|
||||
Чем проще проблема, которую решает инструмент, тем выше его производительность. И большинство тестов не проверяют дополнительные функции, предоставляемые инструментом.
|
||||
Чем проще задача, которую решает инструмент, тем выше его производительность. И большинство бенчмарков не тестируют дополнительные возможности, предоставляемые инструментом.
|
||||
|
||||
Иерархия инструментов имеет следующий вид:
|
||||
Иерархия выглядит так:
|
||||
|
||||
* **Uvicorn**: ASGI-сервер
|
||||
* **Starlette** (использует Uvicorn): веб-микрофреймворк
|
||||
* **FastAPI** (использует Starlette): API-микрофреймворк с дополнительными функциями для создания API, с валидацией данных и т.д.
|
||||
* **Starlette**: (использует Uvicorn) веб-микрофреймворк
|
||||
* **FastAPI**: (использует Starlette) API-микрофреймворк с рядом дополнительных возможностей для создания API, включая валидацию данных и т. п.
|
||||
|
||||
* **Uvicorn**:
|
||||
* Будет иметь наилучшую производительность, так как не имеет большого количества дополнительного кода, кроме самого сервера.
|
||||
* Вы не будете писать приложение на Uvicorn напрямую. Это означало бы, что Ваш код должен включать как минимум весь
|
||||
код, предоставляемый Starlette (или **FastAPI**). И если Вы так сделаете, то в конечном итоге Ваше приложение будет иметь те же накладные расходы, что и при использовании фреймворка, минимизирующего код Вашего приложения и Ваши ошибки.
|
||||
* Uvicorn подлежит сравнению с Daphne, Hypercorn, uWSGI и другими веб-серверами.
|
||||
|
||||
* Будет иметь наилучшую производительность, так как помимо самого сервера у него немного дополнительного кода.
|
||||
* Вы не будете писать приложение непосредственно на Uvicorn. Это означало бы, что Ваш код должен включать как минимум весь код, предоставляемый Starlette (или **FastAPI**). И если Вы так сделаете, то в конечном итоге Ваше приложение будет иметь те же накладные расходы, что и при использовании фреймворка, минимизирующего код Вашего приложения и Ваши ошибки.
|
||||
* Если Вы сравниваете Uvicorn, сравнивайте его с Daphne, Hypercorn, uWSGI и т. д. — серверами приложений.
|
||||
* **Starlette**:
|
||||
* Будет уступать Uvicorn по производительности. Фактически Starlette управляется Uvicorn и из-за выполнения большего количества кода он не может быть быстрее, чем Uvicorn.
|
||||
* Зато он предоставляет Вам инструменты для создания простых веб-приложений с обработкой маршрутов URL и т.д.
|
||||
* Starlette следует сравнивать с Sanic, Flask, Django и другими веб-фреймворками (или микрофреймворками).
|
||||
|
||||
* Будет на следующем месте по производительности после Uvicorn. Фактически Starlette запускается под управлением Uvicorn, поэтому он может быть только «медленнее» Uvicorn из‑за выполнения большего объёма кода.
|
||||
* Зато он предоставляет Вам инструменты для создания простых веб‑приложений с маршрутизацией по путям и т. п.
|
||||
* Если Вы сравниваете Starlette, сравнивайте его с Sanic, Flask, Django и т. д. — веб‑фреймворками (или микрофреймворками).
|
||||
* **FastAPI**:
|
||||
* Так же как Starlette использует Uvicorn и не может быть быстрее него, **FastAPI** использует Starlette, то есть он не может быть быстрее Starlette.
|
||||
* FastAPI предоставляет больше возможностей поверх Starlette, которые наверняка Вам понадобятся при создании API, такие как проверка данных и сериализация. В довесок Вы ещё и получаете автоматическую документацию (автоматическая документация даже не увеличивает накладные расходы при работе приложения, так как она создается при запуске).
|
||||
* Если Вы не используете FastAPI, а используете Starlette напрямую (или другой инструмент вроде Sanic, Flask, Responder и т.д.), Вам пришлось бы самостоятельно реализовать валидацию и сериализацию данных. То есть, в итоге, Ваше приложение имело бы такие же накладные расходы, как если бы оно было создано с использованием FastAPI. И во многих случаях валидация и сериализация данных представляют собой самый большой объём кода, написанного в приложениях.
|
||||
* Таким образом, используя FastAPI Вы потратите меньше времени на разработку, уменьшите количество ошибок, строк кода и, вероятно, получите ту же производительность (или лучше), как и если бы не использовали его (поскольку Вам пришлось бы реализовать все его возможности в своем коде).
|
||||
* FastAPI должно сравнивать с фреймворками веб-приложений (или наборами инструментов), которые обеспечивают валидацию и сериализацию данных, а также предоставляют автоматическую документацию, такими как Flask-apispec, NestJS, Molten и им подобные.
|
||||
* Точно так же, как Starlette использует Uvicorn и не может быть быстрее него, **FastAPI** использует Starlette, поэтому не может быть быстрее его.
|
||||
* FastAPI предоставляет больше возможностей поверх Starlette — те, которые почти всегда нужны при создании API, такие как валидация и сериализация данных. В довесок Вы ещё и получаете автоматическую документацию (автоматическая документация даже не увеличивает накладные расходы при работе приложения, так как она создаётся при запуске).
|
||||
* Если бы Вы не использовали FastAPI, а использовали Starlette напрямую (или другой инструмент вроде Sanic, Flask, Responder и т. д.), Вам пришлось бы самостоятельно реализовать валидацию и сериализацию данных. То есть, в итоге, Ваше приложение имело бы такие же накладные расходы, как если бы оно было создано с использованием FastAPI. И во многих случаях валидация и сериализация данных представляют собой самый большой объём кода, написанного в приложениях.
|
||||
* Таким образом, используя FastAPI, Вы экономите время разработки, уменьшаете количество ошибок, строк кода и, вероятно, получите ту же производительность (или лучше), как и если бы не использовали его (поскольку Вам пришлось бы реализовать все его возможности в своём коде).
|
||||
* Если Вы сравниваете FastAPI, сравнивайте его с фреймворком веб‑приложений (или набором инструментов), который обеспечивает валидацию данных, сериализацию и документацию, такими как Flask-apispec, NestJS, Molten и им подобные. Фреймворки с интегрированной автоматической валидацией данных, сериализацией и документацией.
|
||||
|
||||
@@ -1,323 +1,321 @@
|
||||
# Концепции развёртывания
|
||||
# Концепции развёртывания { #deployments-concepts }
|
||||
|
||||
Существует несколько концепций, применяемых для развёртывания приложений **FastAPI**, равно как и для любых других типов веб-приложений, среди которых вы можете выбрать **наиболее подходящий** способ.
|
||||
При развёртывании приложения **FastAPI** (и вообще любого веб‑API) есть несколько концепций, о которых стоит думать — с их помощью можно выбрать **наиболее подходящий** способ **развёртывания вашего приложения**.
|
||||
|
||||
Самые важные из них:
|
||||
Некоторые из важных концепций:
|
||||
|
||||
* Использование более безопасного протокола HTTPS
|
||||
* Настройки запуска приложения
|
||||
* Перезагрузка приложения
|
||||
* Запуск нескольких экземпляров приложения
|
||||
* Управление памятью
|
||||
* Использование перечисленных функций перед запуском приложения.
|
||||
* Безопасность — HTTPS
|
||||
* Запуск при старте
|
||||
* Перезапуски
|
||||
* Репликация (количество запущенных процессов)
|
||||
* Память
|
||||
* Предварительные шаги перед запуском
|
||||
|
||||
Рассмотрим ниже влияние каждого из них на процесс **развёртывания**.
|
||||
Посмотрим, как они влияют на **развёртывания**.
|
||||
|
||||
Наша конечная цель - **обслуживать клиентов вашего API безопасно** и **бесперебойно**, с максимально эффективным использованием **вычислительных ресурсов** (например, удалённых серверов/виртуальных машин). 🚀
|
||||
В конечном итоге цель — **обслуживать клиентов вашего API** безопасно, **избегать перебоев** и максимально эффективно использовать **вычислительные ресурсы** (например, удалённые серверы/виртуальные машины). 🚀
|
||||
|
||||
Здесь я немного расскажу Вам об этих **концепциях** и надеюсь, что у вас сложится **интуитивное понимание**, какой способ выбрать при развертывании вашего API в различных окружениях, возможно, даже **ещё не существующих**.
|
||||
Здесь я немного расскажу о этих **концепциях**, чтобы у вас появилась **интуиция**, как развёртывать ваш API в разных окружениях, возможно даже в **будущих**, которых ещё не существует.
|
||||
|
||||
Ознакомившись с этими концепциями, вы сможете **оценить и выбрать** лучший способ развёртывании **Вашего API**.
|
||||
Учитывая эти концепции, вы сможете **оценить и спроектировать** лучший способ развёртывания **своих API**.
|
||||
|
||||
В последующих главах я предоставлю Вам **конкретные рецепты** развёртывания приложения FastAPI.
|
||||
В следующих главах я дам более **конкретные рецепты** по развёртыванию приложений FastAPI.
|
||||
|
||||
А сейчас давайте остановимся на важных **идеях этих концепций**. Эти идеи можно также применить и к другим типам веб-приложений. 💡
|
||||
А пока давайте разберём важные **идеи**. Эти концепции применимы и к другим типам веб‑API. 💡
|
||||
|
||||
## Использование более безопасного протокола HTTPS
|
||||
## Безопасность — HTTPS { #security-https }
|
||||
|
||||
В [предыдущей главе об HTTPS](https.md){.internal-link target=_blank} мы рассмотрели, как HTTPS обеспечивает шифрование для вашего API.
|
||||
В [предыдущей главе про HTTPS](https.md){.internal-link target=_blank} мы разобрались, как HTTPS обеспечивает шифрование для вашего API.
|
||||
|
||||
Также мы заметили, что обычно для работы с HTTPS вашему приложению нужен **дополнительный** компонент - **прокси-сервер завершения работы TLS**.
|
||||
Также мы увидели, что HTTPS обычно обеспечивает компонент, **внешний** по отношению к серверу вашего приложения — **TLS Termination Proxy**.
|
||||
|
||||
И если прокси-сервер не умеет сам **обновлять сертификаты HTTPS**, то нужен ещё один компонент для этого действия.
|
||||
И должен быть компонент, отвечающий за **обновление HTTPS‑сертификатов** — это может быть тот же самый компонент или отдельный.
|
||||
|
||||
### Примеры инструментов для работы с HTTPS
|
||||
### Примеры инструментов для HTTPS { #example-tools-for-https }
|
||||
|
||||
Вот некоторые инструменты, которые вы можете применять как прокси-серверы:
|
||||
Некоторые инструменты, которые можно использовать как TLS Termination Proxy:
|
||||
|
||||
* Traefik
|
||||
* С автоматическим обновлением сертификатов ✨
|
||||
* Автоматически обновляет сертификаты ✨
|
||||
* Caddy
|
||||
* С автоматическим обновлением сертификатов ✨
|
||||
* Автоматически обновляет сертификаты ✨
|
||||
* Nginx
|
||||
* С дополнительным компонентом типа Certbot для обновления сертификатов
|
||||
* С внешним компонентом (например, Certbot) для обновления сертификатов
|
||||
* HAProxy
|
||||
* С дополнительным компонентом типа Certbot для обновления сертификатов
|
||||
* Kubernetes с Ingress Controller похожим на Nginx
|
||||
* С дополнительным компонентом типа cert-manager для обновления сертификатов
|
||||
* Использование услуг облачного провайдера (читайте ниже 👇)
|
||||
* С внешним компонентом (например, Certbot) для обновления сертификатов
|
||||
* Kubernetes с Ingress Controller (например, Nginx)
|
||||
* С внешним компонентом (например, cert-manager) для обновления сертификатов
|
||||
* Обрабатывается внутри облачного провайдера как часть его услуг (см. ниже 👇)
|
||||
|
||||
В последнем варианте вы можете воспользоваться услугами **облачного сервиса**, который сделает большую часть работы, включая настройку HTTPS. Это может наложить дополнительные ограничения или потребовать дополнительную плату и т.п. Зато Вам не понадобится самостоятельно заниматься настройками прокси-сервера.
|
||||
Другой вариант — использовать **облачный сервис**, который возьмёт на себя больше задач, включая настройку HTTPS. Там могут быть ограничения или дополнительная стоимость и т.п., но в таком случае вам не придётся самим настраивать TLS Termination Proxy.
|
||||
|
||||
В дальнейшем я покажу Вам некоторые конкретные примеры их применения.
|
||||
В следующих главах я покажу конкретные примеры.
|
||||
|
||||
---
|
||||
|
||||
Следующие концепции рассматривают применение программы, запускающей Ваш API (такой как Uvicorn).
|
||||
Далее рассмотрим концепции, связанные с программой, которая запускает ваш реальный API (например, Uvicorn).
|
||||
|
||||
## Программа и процесс
|
||||
## Программа и процесс { #program-and-process }
|
||||
|
||||
Мы часто будем встречать слова **процесс** и **программа**, потому следует уяснить отличия между ними.
|
||||
Мы часто будем говорить о работающем "**процессе**", поэтому полезно чётко понимать, что это значит и чем отличается от "**программы**".
|
||||
|
||||
### Что такое программа
|
||||
### Что такое программа { #what-is-a-program }
|
||||
|
||||
Термином **программа** обычно описывают множество вещей:
|
||||
Словом **программа** обычно называют разные вещи:
|
||||
|
||||
* **Код**, который вы написали, в нашем случае **Python-файлы**.
|
||||
* **Файл**, который может быть **исполнен** операционной системой, например `python`, `python.exe` или `uvicorn`.
|
||||
* Конкретная программа, **запущенная** операционной системой и использующая центральный процессор и память. В таком случае это также называется **процесс**.
|
||||
* **Код**, который вы пишете, то есть **Python‑файлы**.
|
||||
* **Файл**, который может быть **запущен** операционной системой, например: `python`, `python.exe` или `uvicorn`.
|
||||
* Конкретную программу в момент, когда она **работает** в операционной системе, используя CPU и память. Это также называют **процессом**.
|
||||
|
||||
### Что такое процесс
|
||||
### Что такое процесс { #what-is-a-process }
|
||||
|
||||
Термин **процесс** имеет более узкое толкование, подразумевая что-то, запущенное операционной системой (как в последнем пункте из вышестоящего абзаца):
|
||||
Слово **процесс** обычно используют более конкретно — только для того, что реально выполняется в операционной системе (как в последнем пункте выше):
|
||||
|
||||
* Конкретная программа, **запущенная** операционной системой.
|
||||
* Это не имеет отношения к какому-либо файлу или коду, но нечто **определённое**, управляемое и **выполняемое** операционной системой.
|
||||
* Любая программа, любой код, **могут делать что-то** только когда они **выполняются**. То есть, когда являются **работающим процессом**.
|
||||
* Процесс может быть **прерван** (или "убит") Вами или вашей операционной системой. В результате чего он перестанет исполняться и **не будет продолжать делать что-либо**.
|
||||
* Каждое приложение, которое вы запустили на своём компьютере, каждая программа, каждое "окно" запускает какой-то процесс. И обычно на включенном компьютере **одновременно** запущено множество процессов.
|
||||
* И **одна программа** может запустить **несколько параллельных процессов**.
|
||||
* Конкретная программа в момент, когда она **запущена** в операционной системе.
|
||||
* Речь не о файле и не о коде, а **конкретно** о том, что **исполняется** и управляется операционной системой.
|
||||
* Любая программа, любой код **могут что‑то делать** только когда **исполняются**, то есть когда есть **работающий процесс**.
|
||||
* Процесс можно **завершить** (или «убить») вами или операционной системой. В этот момент он перестаёт выполняться и **больше ничего делать не может**.
|
||||
* У каждого запущенного приложения на вашем компьютере есть свой процесс; у каждой программы, у каждого окна и т.д. Обычно одновременно **работает много процессов**, пока компьютер включён.
|
||||
* Могут **одновременно** работать **несколько процессов** одной и той же **программы**.
|
||||
|
||||
Если вы заглянете в "диспетчер задач" или "системный монитор" (или аналогичные инструменты) вашей операционной системы, то увидите множество работающих процессов.
|
||||
Если вы посмотрите «диспетчер задач» или «системный монитор» (или аналогичные инструменты) в вашей операционной системе, то увидите множество работающих процессов.
|
||||
|
||||
Вполне вероятно, что вы увидите несколько процессов с одним и тем же названием браузерной программы (Firefox, Chrome, Edge и т. Д.). Обычно браузеры запускают один процесс на вкладку и вдобавок некоторые дополнительные процессы.
|
||||
Например, вы, скорее всего, увидите несколько процессов одного и того же браузера (Firefox, Chrome, Edge и т.д.). Обычно браузеры запускают один процесс на вкладку плюс дополнительные процессы.
|
||||
|
||||
<img class="shadow" src="/img/deployment/concepts/image01.png">
|
||||
|
||||
---
|
||||
|
||||
Теперь, когда нам известна разница между **процессом** и **программой**, давайте продолжим обсуждение развёртывания.
|
||||
Теперь, когда мы понимаем разницу между **процессом** и **программой**, продолжим разговор о развёртываниях.
|
||||
|
||||
## Настройки запуска приложения
|
||||
## Запуск при старте { #running-on-startup }
|
||||
|
||||
В большинстве случаев когда вы создаёте веб-приложение, то желаете, чтоб оно **работало постоянно** и непрерывно, предоставляя клиентам доступ в любое время. Хотя иногда у вас могут быть причины, чтоб оно запускалось только при определённых условиях.
|
||||
В большинстве случаев, создавая веб‑API, вы хотите, чтобы он **работал постоянно**, без перерывов, чтобы клиенты всегда могли к нему обратиться. Разве что у вас есть особые причины запускать его только при определённых условиях, но обычно вы хотите, чтобы он был постоянно запущен и **доступен**.
|
||||
|
||||
### Удалённый сервер
|
||||
### На удалённом сервере { #in-a-remote-server }
|
||||
|
||||
Когда вы настраиваете удалённый сервер (облачный сервер, виртуальную машину и т.п.), самое простое, что можно сделать, запустить Uvicorn (или его аналог) вручную, как вы делаете при локальной разработке.
|
||||
Когда вы настраиваете удалённый сервер (облачный сервер, виртуальную машину и т.п.), самый простой вариант — вручную использовать `fastapi run` (он использует Uvicorn) или что‑то похожее, как вы делаете при локальной разработке.
|
||||
|
||||
Это рабочий способ и он полезен **во время разработки**.
|
||||
Это будет работать и полезно **во время разработки**.
|
||||
|
||||
Но если вы потеряете соединение с сервером, то не сможете отслеживать - работает ли всё ещё **запущенный Вами процесс**.
|
||||
Но если соединение с сервером прервётся, **запущенный процесс**, скорее всего, завершится.
|
||||
|
||||
И если сервер перезагрузится (например, после обновления или каких-то действий облачного провайдера), вы скорее всего **этого не заметите**, чтобы снова запустить процесс вручную. Вследствие этого Ваш API останется мёртвым. 😱
|
||||
А если сервер перезагрузится (например, после обновлений или миграций у облачного провайдера), вы, вероятно, **даже не заметите этого**. Из‑за этого вы не узнаете, что нужно вручную перезапустить процесс — и ваш API просто будет «мёртв». 😱
|
||||
|
||||
### Автоматический запуск программ
|
||||
### Автоматический запуск при старте { #run-automatically-on-startup }
|
||||
|
||||
Вероятно вы захотите, чтоб Ваша серверная программа (такая, как Uvicorn) стартовала автоматически при включении сервера, без **человеческого вмешательства** и всегда могла управлять Вашим API (так как Uvicorn запускает приложение FastAPI).
|
||||
Как правило, вы захотите, чтобы серверная программа (например, Uvicorn) запускалась автоматически при старте сервера и без **участия человека**, чтобы всегда был процесс, запущенный с вашим API (например, Uvicorn, запускающий ваше приложение FastAPI).
|
||||
|
||||
### Отдельная программа
|
||||
### Отдельная программа { #separate-program }
|
||||
|
||||
Для этого у обычно используют отдельную программу, которая следит за тем, чтобы Ваши приложения запускались при включении сервера. Такой подход гарантирует, что другие компоненты или приложения также будут запущены, например, база данных
|
||||
Чтобы этого добиться, обычно используют **отдельную программу**, которая гарантирует запуск вашего приложения при старте. Во многих случаях она также запускает и другие компоненты/приложения, например базу данных.
|
||||
|
||||
### Примеры инструментов, управляющих запуском программ
|
||||
### Примеры инструментов для запуска при старте { #example-tools-to-run-at-startup }
|
||||
|
||||
Вот несколько примеров, которые могут справиться с такой задачей:
|
||||
Примеры инструментов, которые могут с этим справиться:
|
||||
|
||||
* Docker
|
||||
* Kubernetes
|
||||
* Docker Compose
|
||||
* Docker в режиме Swarm
|
||||
* Docker в режиме Swarm (Swarm Mode)
|
||||
* Systemd
|
||||
* Supervisor
|
||||
* Использование услуг облачного провайдера
|
||||
* Обработка внутри облачного провайдера как часть его услуг
|
||||
* Прочие...
|
||||
|
||||
Я покажу Вам некоторые примеры их использования в следующих главах.
|
||||
Более конкретные примеры будут в следующих главах.
|
||||
|
||||
## Перезапуск
|
||||
## Перезапуски { #restarts }
|
||||
|
||||
Вы, вероятно, также захотите, чтоб ваше приложение **перезапускалось**, если в нём произошёл сбой.
|
||||
Подобно тому как вы обеспечиваете запуск приложения при старте, вы, вероятно, захотите обеспечить его **перезапуск** после сбоев.
|
||||
|
||||
### Мы ошибаемся
|
||||
### Мы ошибаемся { #we-make-mistakes }
|
||||
|
||||
Все люди совершают **ошибки**. Программное обеспечение почти *всегда* содержит **баги** спрятавшиеся в разных местах. 🐛
|
||||
Мы, люди, постоянно совершаем **ошибки**. В программном обеспечении почти всегда есть **баги**, скрытые в разных местах. 🐛
|
||||
|
||||
И мы, будучи разработчиками, продолжаем улучшать код, когда обнаруживаем в нём баги или добавляем новый функционал (возможно, добавляя при этом баги 😅).
|
||||
И мы, как разработчики, продолжаем улучшать код — находим баги и добавляем новые возможности (иногда добавляя новые баги 😅).
|
||||
|
||||
### Небольшие ошибки обрабатываются автоматически
|
||||
### Небольшие ошибки обрабатываются автоматически { #small-errors-automatically-handled }
|
||||
|
||||
Когда вы создаёте свои API на основе FastAPI и допускаете в коде ошибку, то FastAPI обычно остановит её распространение внутри одного запроса, при обработке которого она возникла. 🛡
|
||||
Создавая веб‑API с FastAPI, если в нашем коде возникает ошибка, FastAPI обычно «локализует» её в пределах одного запроса, который эту ошибку вызвал. 🛡
|
||||
|
||||
Клиент получит ошибку **500 Internal Server Error** в ответ на свой запрос, но приложение не сломается и будет продолжать работать с последующими запросами.
|
||||
Клиент получит **500 Internal Server Error** для этого запроса, но приложение продолжит работать для последующих запросов, а не «упадёт» целиком.
|
||||
|
||||
### Большие ошибки - Падение приложений
|
||||
### Большие ошибки — падения { #bigger-errors-crashes }
|
||||
|
||||
Тем не менее, может случиться так, что ошибка вызовет **сбой всего приложения** или даже сбой в Uvicorn, а то и в самом Python. 💥
|
||||
Тем не менее возможны случаи, когда код **роняет всё приложение**, приводя к сбою Uvicorn и Python. 💥
|
||||
|
||||
Но мы всё ещё хотим, чтобы приложение **продолжало работать** несмотря на эту единственную ошибку, обрабатывая, как минимум, запросы к *операциям пути* не имеющим ошибок.
|
||||
И вы, скорее всего, не захотите, чтобы приложение оставалось «мёртвым» из‑за ошибки в одном месте — вы захотите, чтобы оно **продолжало работать** хотя бы для *операций пути*, которые не сломаны.
|
||||
|
||||
### Перезапуск после падения
|
||||
### Перезапуск после падения { #restart-after-crash }
|
||||
|
||||
Для случаев, когда ошибки приводят к сбою в запущенном **процессе**, Вам понадобится добавить компонент, который **перезапустит** процесс хотя бы пару раз...
|
||||
В случаях действительно серьёзных ошибок, которые роняют работающий **процесс**, вам понадобится внешний компонент, отвечающий за **перезапуск** процесса, как минимум пару раз...
|
||||
|
||||
/// tip | Заметка
|
||||
/// tip | Совет
|
||||
|
||||
... Если приложение падает сразу же после запуска, вероятно бесполезно его бесконечно перезапускать. Но полагаю, вы заметите такое поведение во время разработки или, по крайней мере, сразу после развёртывания.
|
||||
...Хотя если приложение **падает сразу же**, вероятно, нет смысла перезапускать его бесконечно. Но такие случаи вы, скорее всего, заметите во время разработки или как минимум сразу после развёртывания.
|
||||
|
||||
Так что давайте сосредоточимся на конкретных случаях, когда приложение может полностью выйти из строя, но всё ещё есть смысл его запустить заново.
|
||||
Давайте сосредоточимся на основных сценариях, когда в каких‑то конкретных ситуациях **в будущем** приложение может падать целиком, и при этом имеет смысл его перезапускать.
|
||||
|
||||
///
|
||||
|
||||
Возможно вы захотите, чтоб был некий **внешний компонент**, ответственный за перезапуск вашего приложения даже если уже не работает Uvicorn или Python. То есть ничего из того, что написано в вашем коде внутри приложения, не может быть выполнено в принципе.
|
||||
Скорее всего, вы захотите, чтобы перезапуском вашего приложения занимался **внешний компонент**, потому что к тому моменту Uvicorn и Python уже упали, и внутри того же кода вашего приложения сделать уже ничего нельзя.
|
||||
|
||||
### Примеры инструментов для автоматического перезапуска
|
||||
### Примеры инструментов для автоматического перезапуска { #example-tools-to-restart-automatically }
|
||||
|
||||
В большинстве случаев инструменты **запускающие программы при старте сервера** умеют **перезапускать** эти программы.
|
||||
В большинстве случаев тот же инструмент, который **запускает программу при старте**, умеет обрабатывать и автоматические **перезапуски**.
|
||||
|
||||
В качестве примера можно взять те же:
|
||||
Например, это может быть:
|
||||
|
||||
* Docker
|
||||
* Kubernetes
|
||||
* Docker Compose
|
||||
* Docker в режиме Swarm
|
||||
* Docker в режиме Swarm (Swarm Mode)
|
||||
* Systemd
|
||||
* Supervisor
|
||||
* Использование услуг облачного провайдера
|
||||
* Обработка внутри облачного провайдера как часть его услуг
|
||||
* Прочие...
|
||||
|
||||
## Запуск нескольких экземпляров приложения (Репликация) - Процессы и память
|
||||
## Репликация — процессы и память { #replication-processes-and-memory }
|
||||
|
||||
Приложение FastAPI, управляемое серверной программой (такой как Uvicorn), запускается как **один процесс** и может обслуживать множество клиентов одновременно.
|
||||
В приложении FastAPI, используя серверную программу (например, команду `fastapi`, которая запускает Uvicorn), запуск в **одном процессе** уже позволяет обслуживать нескольких клиентов одновременно.
|
||||
|
||||
Но часто Вам может понадобиться несколько одновременно работающих одинаковых процессов.
|
||||
Но во многих случаях вы захотите одновременно запустить несколько процессов‑воркеров.
|
||||
|
||||
### Множество процессов - Воркеры (Workers)
|
||||
### Несколько процессов — Воркеры { #multiple-processes-workers }
|
||||
|
||||
Если количество Ваших клиентов больше, чем может обслужить один процесс (допустим, что виртуальная машина не слишком мощная), но при этом Вам доступно **несколько ядер процессора**, то вы можете запустить **несколько процессов** одного и того же приложения параллельно и распределить запросы между этими процессами.
|
||||
Если клиентов больше, чем способен обслужить один процесс (например, если виртуальная машина не слишком мощная), и на сервере есть **несколько ядер CPU**, вы можете запустить **несколько процессов** одного и того же приложения параллельно и распределять запросы между ними.
|
||||
|
||||
**Несколько запущенных процессов** одной и той же API-программы часто называют **воркерами**.
|
||||
Когда вы запускаете **несколько процессов** одной и той же программы API, их обычно называют **воркерами**.
|
||||
|
||||
### Процессы и порты́
|
||||
### Процессы‑воркеры и порты { #worker-processes-and-ports }
|
||||
|
||||
Помните ли Вы, как на странице [Об HTTPS](https.md){.internal-link target=_blank} мы обсуждали, что на сервере только один процесс может слушать одну комбинацию IP-адреса и порта?
|
||||
Помните из раздела [Об HTTPS](https.md){.internal-link target=_blank}, что на сервере только один процесс может слушать конкретную комбинацию порта и IP‑адреса?
|
||||
|
||||
С тех пор ничего не изменилось.
|
||||
Это по‑прежнему так.
|
||||
|
||||
Соответственно, чтобы иметь возможность работать с **несколькими процессами** одновременно, должен быть **один процесс, прослушивающий порт** и затем каким-либо образом передающий данные каждому рабочему процессу.
|
||||
Поэтому, чтобы одновременно работало **несколько процессов**, должен быть **один процесс, слушающий порт**, который затем каким‑то образом передаёт коммуникацию каждому воркер‑процессу.
|
||||
|
||||
### У каждого процесса своя память
|
||||
### Память на процесс { #memory-per-process }
|
||||
|
||||
Работающая программа загружает в память данные, необходимые для её работы, например, переменные содержащие модели машинного обучения или большие файлы. Каждая переменная **потребляет некоторое количество оперативной памяти (RAM)** сервера.
|
||||
Когда программа загружает что‑то в память (например, модель машинного обучения в переменную или содержимое большого файла в переменную), всё это **потребляет часть памяти (RAM)** сервера.
|
||||
|
||||
Обычно процессы **не делятся памятью друг с другом**. Сие означает, что каждый работающий процесс имеет свои данные, переменные и свой кусок памяти. И если для выполнения вашего кода процессу нужно много памяти, то **каждый такой же процесс** запущенный дополнительно, потребует такого же количества памяти.
|
||||
И разные процессы обычно **не делят память**. Это значит, что у каждого процесса свои переменные и своя память. Если ваш код потребляет много памяти, то **каждый процесс** будет потреблять сопоставимый объём памяти.
|
||||
|
||||
### Память сервера
|
||||
### Память сервера { #server-memory }
|
||||
|
||||
Допустим, что Ваш код загружает модель машинного обучения **размером 1 ГБ**. Когда вы запустите своё API как один процесс, он займёт в оперативной памяти не менее 1 ГБ. А если вы запустите **4 таких же процесса** (4 воркера), то каждый из них займёт 1 ГБ оперативной памяти. В результате вашему API потребуется **4 ГБ оперативной памяти (RAM)**.
|
||||
Например, если ваш код загружает модель Машинного обучения размером **1 ГБ**, то при запуске одного процесса с вашим API он будет использовать как минимум 1 ГБ RAM. А если вы запустите **4 процесса** (4 воркера), каждый процесс будет использовать 1 ГБ RAM. Всего ваш API будет потреблять **4 ГБ RAM**.
|
||||
|
||||
И если Ваш удалённый сервер или виртуальная машина располагает только 3 ГБ памяти, то попытка загрузить в неё 4 ГБ данных вызовет проблемы. 🚨
|
||||
И если у вашего удалённого сервера или виртуальной машины только 3 ГБ RAM, попытка загрузить более 4 ГБ вызовет проблемы. 🚨
|
||||
|
||||
### Множество процессов - Пример
|
||||
### Несколько процессов — пример { #multiple-processes-an-example }
|
||||
|
||||
В этом примере **менеджер процессов** запустит и будет управлять двумя **воркерами**.
|
||||
В этом примере есть **процесс‑менеджер**, который запускает и контролирует два **процесса‑воркера**.
|
||||
|
||||
Менеджер процессов будет слушать определённый **сокет** (IP:порт) и передавать данные работающим процессам.
|
||||
Процесс‑менеджер, вероятно, будет тем, кто слушает **порт** на IP. И он будет передавать всю коммуникацию воркер‑процессам.
|
||||
|
||||
Каждый из этих процессов будет запускать ваше приложение для обработки полученного **запроса** и возвращения вычисленного **ответа** и они будут использовать оперативную память.
|
||||
Эти воркеры будут запускать ваше приложение, выполнять основные вычисления для получения **запроса** и возврата **ответа**, и загружать всё, что вы кладёте в переменные, в RAM.
|
||||
|
||||
<img src="/img/deployment/concepts/process-ram.drawio.svg">
|
||||
|
||||
Безусловно, на этом же сервере будут работать и **другие процессы**, которые не относятся к вашему приложению.
|
||||
Конечно, на той же машине помимо вашего приложения, скорее всего, будут работать и **другие процессы**.
|
||||
|
||||
Интересная деталь заключается в том, что процент **использования центрального процессора (CPU)** каждым процессом может сильно меняться с течением времени, но объём занимаемой **оперативной памяти (RAM)** остаётся относительно **стабильным**.
|
||||
Интересная деталь: процент **использования CPU** каждым процессом со временем может сильно **меняться**, но **память (RAM)** обычно остаётся более‑менее **стабильной**.
|
||||
|
||||
Если у вас есть API, который каждый раз выполняет сопоставимый объем вычислений, и у вас много клиентов, то **загрузка процессора**, вероятно, *также будет стабильной* (вместо того, чтобы постоянно быстро увеличиваться и уменьшаться).
|
||||
Если у вас API, который каждый раз выполняет сопоставимый объём вычислений, и у вас много клиентов, то **загрузка процессора**, вероятно, *тоже будет стабильной* (вместо того, чтобы быстро и постоянно «скакать»).
|
||||
|
||||
### Примеры стратегий и инструментов для запуска нескольких экземпляров приложения
|
||||
### Примеры инструментов и стратегий репликации { #examples-of-replication-tools-and-strategies }
|
||||
|
||||
Существует несколько подходов для достижения целей репликации и я расскажу Вам больше о конкретных стратегиях в следующих главах, например, когда речь пойдет о Docker и контейнерах.
|
||||
Есть несколько подходов для достижения этого, и я расскажу больше о конкретных стратегиях в следующих главах, например, говоря о Docker и контейнерах.
|
||||
|
||||
Основное ограничение при этом - только **один** компонент может работать с определённым **портом публичного IP**. И должен быть способ **передачи** данных между этим компонентом и копиями **процессов/воркеров**.
|
||||
Главное ограничение: должен быть **один** компонент, который обрабатывает **порт** на **публичном IP**. И у него должен быть способ **передавать** коммуникацию реплицированным **процессам/воркерам**.
|
||||
|
||||
Вот некоторые возможные комбинации и стратегии:
|
||||
Некоторые возможные комбинации и стратегии:
|
||||
|
||||
* **Gunicorn** управляющий **воркерами Uvicorn**
|
||||
* Gunicorn будет выступать как **менеджер процессов**, прослушивая **IP:port**. Необходимое количество запущенных экземпляров приложения будет осуществляться посредством запуска **множества работающих процессов Uvicorn**.
|
||||
* **Uvicorn** управляющий **воркерами Uvicorn**
|
||||
* Один процесс Uvicorn будет выступать как **менеджер процессов**, прослушивая **IP:port**. Он будет запускать **множество работающих процессов Uvicorn**.
|
||||
* **Kubernetes** и аналогичные **контейнерные системы**
|
||||
* Какой-то компонент в **Kubernetes** будет слушать **IP:port**. Необходимое количество запущенных экземпляров приложения будет осуществляться посредством запуска **нескольких контейнеров**, в каждом из которых работает **один процесс Uvicorn**.
|
||||
* **Облачные сервисы**, которые позаботятся обо всём за Вас
|
||||
* Возможно, что облачный сервис умеет **управлять запуском дополнительных экземпляров приложения**. Вероятно, он потребует, чтоб вы указали - какой **процесс** или **образ** следует клонировать. Скорее всего, вы укажете **один процесс Uvicorn** и облачный сервис будет запускать его копии при необходимости.
|
||||
* **Uvicorn** с `--workers`
|
||||
* Один **процесс‑менеджер** Uvicorn будет слушать **IP** и **порт** и запускать **несколько процессов‑воркеров Uvicorn**.
|
||||
* **Kubernetes** и другие распределённые **контейнерные системы**
|
||||
* Некий компонент на уровне **Kubernetes** будет слушать **IP** и **порт**. Репликация достигается с помощью **нескольких контейнеров**, в каждом из которых работает **один процесс Uvicorn**.
|
||||
* **Облачные сервисы**, которые берут это на себя
|
||||
* Облачный сервис, скорее всего, **возьмёт репликацию на себя**. Он, возможно, позволит указать **процесс для запуска** или **образ контейнера**. В любом случае это, скорее всего, будет **один процесс Uvicorn**, а сервис займётся его репликацией.
|
||||
|
||||
/// tip | Заметка
|
||||
/// tip | Совет
|
||||
|
||||
Если вы не знаете, что такое **контейнеры**, Docker или Kubernetes, не переживайте.
|
||||
Не беспокойтесь, если некоторые пункты про **контейнеры**, Docker или Kubernetes пока кажутся неочевидными.
|
||||
|
||||
Я поведаю Вам о контейнерах, образах, Docker, Kubernetes и т.п. в главе: [FastAPI внутри контейнеров - Docker](docker.md){.internal-link target=_blank}.
|
||||
Я расскажу больше про образы контейнеров, Docker, Kubernetes и т.п. в следующей главе: [FastAPI внутри контейнеров — Docker](docker.md){.internal-link target=_blank}.
|
||||
|
||||
///
|
||||
|
||||
## Шаги, предшествующие запуску
|
||||
## Предварительные шаги перед запуском { #previous-steps-before-starting }
|
||||
|
||||
Часто бывает, что Вам необходимо произвести какие-то подготовительные шаги **перед запуском** своего приложения.
|
||||
Во многих случаях вы захотите выполнить некоторые шаги **перед запуском** приложения.
|
||||
|
||||
Например, запустить **миграции базы данных**.
|
||||
|
||||
Но в большинстве случаев такие действия достаточно произвести **однократно**.
|
||||
Но чаще всего эти шаги нужно выполнять только **один раз**.
|
||||
|
||||
Поэтому Вам нужен будет **один процесс**, выполняющий эти **подготовительные шаги** до запуска приложения.
|
||||
Поэтому вы захотите иметь **один процесс**, который выполнит эти **предварительные шаги**, прежде чем запускать приложение.
|
||||
|
||||
Также Вам нужно будет убедиться, что этот процесс выполнил подготовительные шаги *даже* если впоследствии вы запустите **несколько процессов** (несколько воркеров) самого приложения. Если бы эти шаги выполнялись в каждом **клонированном процессе**, они бы **дублировали** работу, пытаясь выполнить её **параллельно**. И если бы эта работа была бы чем-то деликатным, вроде миграции базы данных, то это может вызвать конфликты между ними.
|
||||
И вам нужно будет убедиться, что это делает один процесс **даже** если потом вы запускаете **несколько процессов** (несколько воркеров) самого приложения. Если эти шаги выполнят **несколько процессов**, они **дублируют** работу, запустив её **параллельно**, и, если речь о чём‑то деликатном (например, миграции БД), это может вызвать конфликты.
|
||||
|
||||
Безусловно, возможны случаи, когда нет проблем при выполнении предварительной подготовки параллельно или несколько раз. Тогда Вам повезло, работать с ними намного проще.
|
||||
Конечно, бывают случаи, когда нет проблем, если предварительные шаги выполняются несколько раз — тогда всё проще.
|
||||
|
||||
/// tip | Заметка
|
||||
/// tip | Совет
|
||||
|
||||
Имейте в виду, что в некоторых случаях запуск вашего приложения **может не требовать каких-либо предварительных шагов вовсе**.
|
||||
Также учтите, что в зависимости от вашей схемы развёртывания в некоторых случаях **предварительные шаги могут вовсе не требоваться**.
|
||||
|
||||
Что ж, тогда Вам не нужно беспокоиться об этом. 🤷
|
||||
Тогда об этом можно не беспокоиться. 🤷
|
||||
|
||||
///
|
||||
|
||||
### Примеры стратегий запуска предварительных шагов
|
||||
### Примеры стратегий для предварительных шагов { #examples-of-previous-steps-strategies }
|
||||
|
||||
Существует **сильная зависимость** от того, как вы **развёртываете свою систему**, запускаете программы, обрабатываете перезапуски и т.д.
|
||||
Это будет **сильно зависеть** от того, как вы **развёртываете систему**, как запускаете программы, обрабатываете перезапуски и т.д.
|
||||
|
||||
Вот некоторые возможные идеи:
|
||||
Некоторые возможные идеи:
|
||||
|
||||
* При использовании Kubernetes нужно предусмотреть "инициализирующий контейнер", запускаемый до контейнера с приложением.
|
||||
* Bash-скрипт, выполняющий предварительные шаги, а затем запускающий приложение.
|
||||
* При этом Вам всё ещё нужно найти способ - как запускать/перезапускать *такой* bash-скрипт, обнаруживать ошибки и т.п.
|
||||
* «Init Container» в Kubernetes, который запускается перед контейнером с приложением
|
||||
* Bash‑скрипт, который выполняет предварительные шаги, а затем запускает приложение
|
||||
* При этом всё равно нужен способ запускать/перезапускать *этот* bash‑скрипт, обнаруживать ошибки и т.п.
|
||||
|
||||
/// tip | Заметка
|
||||
/// tip | Совет
|
||||
|
||||
Я приведу Вам больше конкретных примеров работы с контейнерами в главе: [FastAPI внутри контейнеров - Docker](docker.md){.internal-link target=_blank}.
|
||||
Я приведу более конкретные примеры с контейнерами в следующей главе: [FastAPI внутри контейнеров — Docker](docker.md){.internal-link target=_blank}.
|
||||
|
||||
///
|
||||
|
||||
## Утилизация ресурсов
|
||||
## Использование ресурсов { #resource-utilization }
|
||||
|
||||
Ваш сервер располагает ресурсами, которые Ваши программы могут потреблять или **утилизировать**, а именно - время работы центрального процессора и объём оперативной памяти.
|
||||
Ваш сервер(а) — это **ресурс**, который ваши программы могут потреблять или **использовать**: время вычислений на CPU и доступную оперативную память (RAM).
|
||||
|
||||
Как много системных ресурсов вы предполагаете потребить/утилизировать? Если не задумываться, то можно ответить - "немного", но на самом деле Вы, вероятно, захотите использовать **максимально возможное количество**.
|
||||
Какую долю системных ресурсов вы хотите потреблять/использовать? Можно подумать «немного», но на практике вы, скорее всего, захотите потреблять **максимум без падений**.
|
||||
|
||||
Если вы платите за содержание трёх серверов, но используете лишь малую часть системных ресурсов каждого из них, то вы **выбрасываете деньги на ветер**, а также **впустую тратите электроэнергию** и т.п.
|
||||
Если вы платите за 3 сервера, но используете лишь малую часть их RAM и CPU, вы, вероятно, **тратите деньги впустую** 💸 и **электроэнергию серверов** 🌎 и т.п.
|
||||
|
||||
В таком случае было бы лучше обойтись двумя серверами, но более полно утилизировать их ресурсы (центральный процессор, оперативную память, жёсткий диск, сети передачи данных и т.д).
|
||||
В таком случае лучше иметь 2 сервера и использовать более высокий процент их ресурсов (CPU, память, диск, сетевую полосу и т.д.).
|
||||
|
||||
С другой стороны, если вы располагаете только двумя серверами и используете **на 100% их процессоры и память**, но какой-либо процесс запросит дополнительную память, то операционная система сервера будет использовать жёсткий диск для расширения оперативной памяти (а диск работает в тысячи раз медленнее), а то вовсе **упадёт**. Или если какому-то процессу понадобится произвести вычисления, то ему придётся подождать, пока процессор освободится.
|
||||
С другой стороны, если у вас 2 сервера и вы используете **100% их CPU и RAM**, в какой‑то момент один процесс попросит больше памяти, и сервер начнёт использовать диск как «память» (что в тысячи раз медленнее) или даже **упадёт**. Или процессу понадобятся вычисления, но ему придётся ждать освобождения CPU.
|
||||
|
||||
В такой ситуации лучше подключить **ещё один сервер** и перераспределить процессы между серверами, чтоб всем **хватало памяти и процессорного времени**.
|
||||
В таком случае лучше добавить **ещё один сервер** и запустить часть процессов на нём, чтобы у всех было **достаточно RAM и времени CPU**.
|
||||
|
||||
Также есть вероятность, что по какой-то причине возник **всплеск** запросов к вашему API. Возможно, это был вирус, боты или другие сервисы начали пользоваться им. И для таких происшествий вы можете захотеть иметь дополнительные ресурсы.
|
||||
Также возможен **всплеск** использования вашего API: он мог «взорваться» по популярности, или какие‑то сервисы/боты начали его активно использовать. На такие случаи стоит иметь запас ресурсов.
|
||||
|
||||
При настройке логики развёртываний, вы можете указать **целевое значение** утилизации ресурсов, допустим, **от 50% до 90%**. Обычно эти метрики и используют.
|
||||
Можно задать **целевое значение**, например **между 50% и 90%** использования ресурсов. Скорее всего, именно эти вещи вы будете измерять и на их основе настраивать развёртывание.
|
||||
|
||||
Вы можете использовать простые инструменты, такие как `htop`, для отслеживания загрузки центрального процессора и оперативной памяти сервера, в том числе каждым процессом. Или более сложные системы мониторинга нескольких серверов.
|
||||
Можно использовать простые инструменты вроде `htop`, чтобы смотреть загрузку CPU и RAM на сервере или по процессам. Или более сложные распределённые системы мониторинга.
|
||||
|
||||
## Резюме
|
||||
## Резюме { #recap }
|
||||
|
||||
Вы прочитали некоторые из основных концепций, которые необходимо иметь в виду при принятии решения о развертывании приложений:
|
||||
Здесь вы прочитали о некоторых основных концепциях, которые, вероятно, стоит учитывать при выборе способа развёртывания приложения:
|
||||
|
||||
* Использование более безопасного протокола HTTPS
|
||||
* Настройки запуска приложения
|
||||
* Перезагрузка приложения
|
||||
* Запуск нескольких экземпляров приложения
|
||||
* Управление памятью
|
||||
* Использование перечисленных функций перед запуском приложения.
|
||||
* Безопасность — HTTPS
|
||||
* Запуск при старте
|
||||
* Перезапуски
|
||||
* Репликация (количество запущенных процессов)
|
||||
* Память
|
||||
* Предварительные шаги перед запуском
|
||||
|
||||
Осознание этих идей и того, как их применять, должно дать Вам интуитивное понимание, необходимое для принятия решений при настройке развертываний. 🤓
|
||||
Понимание этих идей и того, как их применять, даст вам интуицию, необходимую для принятия решений при настройке и доработке ваших развёртываний. 🤓
|
||||
|
||||
В следующих разделах я приведу более конкретные примеры возможных стратегий, которым вы можете следовать. 🚀
|
||||
В следующих разделах я приведу более конкретные примеры возможных стратегий. 🚀
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
# FastAPI и Docker-контейнеры
|
||||
# FastAPI в контейнерах — Docker { #fastapi-in-containers-docker }
|
||||
|
||||
При развёртывании приложений FastAPI, часто начинают с создания **образа контейнера на основе Linux**. Обычно для этого используют <a href="https://www.docker.com/" class="external-link" target="_blank">**Docker**</a>. Затем можно развернуть такой контейнер на сервере одним из нескольких способов.
|
||||
При развёртывании приложений FastAPI распространённый подход — собирать **образ контейнера на Linux**. Обычно это делают с помощью <a href="https://www.docker.com/" class="external-link" target="_blank">**Docker**</a>. Затем такой образ контейнера можно развернуть несколькими способами.
|
||||
|
||||
Использование контейнеров на основе Linux имеет ряд преимуществ, включая **безопасность**, **воспроизводимость**, **простоту** и прочие.
|
||||
Использование Linux-контейнеров даёт ряд преимуществ: **безопасность**, **воспроизводимость**, **простоту** и другие.
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Торопитесь или уже знакомы с этой технологией? Перепрыгните на раздел [Создать Docker-образ для FastAPI 👇](#docker-fastapi)
|
||||
Нет времени и вы уже знакомы с этим? Перейдите к [`Dockerfile` ниже 👇](#build-a-docker-image-for-fastapi).
|
||||
|
||||
///
|
||||
|
||||
<details>
|
||||
<summary>Развернуть Dockerfile 👀</summary>
|
||||
<summary>Предпросмотр Dockerfile 👀</summary>
|
||||
|
||||
```Dockerfile
|
||||
FROM python:3.9
|
||||
@@ -24,130 +24,125 @@ RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
||||
|
||||
COPY ./app /code/app
|
||||
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
|
||||
CMD ["fastapi", "run", "app/main.py", "--port", "80"]
|
||||
|
||||
# Если используете прокси-сервер, такой как Nginx или Traefik, добавьте --proxy-headers
|
||||
# CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80", "--proxy-headers"]
|
||||
# Если запускаете за прокси, например Nginx или Traefik, добавьте --proxy-headers
|
||||
# CMD ["fastapi", "run", "app/main.py", "--port", "80", "--proxy-headers"]
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Что такое "контейнер"
|
||||
## Что такое контейнер { #what-is-a-container }
|
||||
|
||||
Контейнеризация - это **легковесный** способ упаковать приложение, включая все его зависимости и необходимые файлы, чтобы изолировать его от других контейнеров (других приложений и компонентов) работающих на этой же системе.
|
||||
Контейнеры (в основном Linux-контейнеры) — это очень **легковесный** способ упаковать приложения вместе со всеми их зависимостями и необходимыми файлами, изолировав их от других контейнеров (других приложений или компонентов) в той же системе.
|
||||
|
||||
Контейнеры, основанные на Linux, запускаются используя ядро Linux хоста (машины, виртуальной машины, облачного сервера и т.п.). Это значит, что они очень легковесные (по сравнению с полноценными виртуальными машинами, полностью эмулирующими работу операционной системы).
|
||||
Linux-контейнеры запускаются, используя то же ядро Linux хоста (машины, виртуальной машины, облачного сервера и т.п.). Это означает, что они очень легковесные (по сравнению с полноценными виртуальными машинами, эмулирующими целую операционную систему).
|
||||
|
||||
Благодаря этому, контейнеры потребляют **малое количество ресурсов**, сравнимое с процессом запущенным напрямую (виртуальная машина потребует гораздо больше ресурсов).
|
||||
Таким образом, контейнеры потребляют **малое количество ресурсов**, сопоставимое с запуском процессов напрямую (виртуальная машина потребовала бы намного больше ресурсов).
|
||||
|
||||
Контейнеры также имеют собственные запущенные **изолированные** процессы (но часто только один процесс), файловую систему и сеть, что упрощает развёртывание, разработку, управление доступом и т.п.
|
||||
У контейнеров также есть собственные **изолированные** выполняемые процессы (обычно всего один процесс), файловая система и сеть, что упрощает развёртывание, безопасность, разработку и т.д.
|
||||
|
||||
## Что такое "образ контейнера"
|
||||
## Что такое образ контейнера { #what-is-a-container-image }
|
||||
|
||||
Для запуска **контейнера** нужен **образ контейнера**.
|
||||
**Контейнер** запускается из **образа контейнера**.
|
||||
|
||||
Образ контейнера - это **замороженная** версия всех файлов, переменных окружения, программ и команд по умолчанию, необходимых для работы приложения. **Замороженный** - означает, что **образ** не запущен и не выполняется, это всего лишь упакованные вместе файлы и метаданные.
|
||||
Образ контейнера — это **статическая** версия всех файлов, переменных окружения и команды/программы по умолчанию, которые должны присутствовать в контейнере. Здесь **статическая** означает, что **образ** не запущен, он не выполняется — это только упакованные файлы и метаданные.
|
||||
|
||||
В отличие от **образа контейнера**, хранящего неизменное содержимое, под термином **контейнер** подразумевают запущенный образ, то есть объёкт, который **исполняется**.
|
||||
В противоположность «**образу контейнера**» (хранящему статическое содержимое), «**контейнер**» обычно означает запущенный экземпляр, то, что **выполняется**.
|
||||
|
||||
Когда **контейнер** запущен (на основании **образа**), он может создавать и изменять файлы, переменные окружения и т.д. Эти изменения будут существовать только внутри контейнера, но не будут сохраняться в образе контейнера (не будут сохранены на диск).
|
||||
Когда **контейнер** запущен (на основе **образа контейнера**), он может создавать или изменять файлы, переменные окружения и т.д.. Эти изменения существуют только внутри контейнера и не сохраняются в исходном образе контейнера (не записываются на диск).
|
||||
|
||||
Образ контейнера можно сравнить с файлом, содержащем **программу**, например, как файл `main.py`.
|
||||
Образ контейнера можно сравнить с **файлами программы**, например `python` и каким-то файлом `main.py`.
|
||||
|
||||
И **контейнер** (в отличие от **образа**) - это на самом деле выполняемый экземпляр образа, примерно как **процесс**. По факту, контейнер запущен только когда запущены его процессы (чаще, всего один процесс) и остановлен, когда запущенных процессов нет.
|
||||
А сам **контейнер** (в отличие от **образа контейнера**) — это фактически запущенный экземпляр образа, сопоставимый с **процессом**. По сути, контейнер работает только тогда, когда в нём есть **запущенный процесс** (и обычно это один процесс). Контейнер останавливается, когда в нём не остаётся запущенных процессов.
|
||||
|
||||
## Образы контейнеров
|
||||
## Образы контейнеров { #container-images }
|
||||
|
||||
Docker является одним из основных инструментов для создания **образов** и **контейнеров** и управления ими.
|
||||
Docker — один из основных инструментов для создания и управления **образами контейнеров** и **контейнерами**.
|
||||
|
||||
Существует общедоступный <a href="https://hub.docker.com/" class="external-link" target="_blank">Docker Hub</a> с подготовленными **официальными образами** многих инструментов, окружений, баз данных и приложений.
|
||||
Существует публичный <a href="https://hub.docker.com/" class="external-link" target="_blank">Docker Hub</a> с готовыми **официальными образами** для многих инструментов, окружений, баз данных и приложений.
|
||||
|
||||
К примеру, есть официальный <a href="https://hub.docker.com/_/python" class="external-link" target="_blank">образ Python</a>.
|
||||
Например, есть официальный <a href="https://hub.docker.com/_/python" class="external-link" target="_blank">образ Python</a>.
|
||||
|
||||
Также там представлены и другие полезные образы, такие как базы данных:
|
||||
А также множество образов для разных вещей, например баз данных:
|
||||
|
||||
* <a href="https://hub.docker.com/_/postgres" class="external-link" target="_blank">PostgreSQL</a>
|
||||
* <a href="https://hub.docker.com/_/mysql" class="external-link" target="_blank">MySQL</a>
|
||||
* <a href="https://hub.docker.com/_/mongo" class="external-link" target="_blank">MongoDB</a>
|
||||
* <a href="https://hub.docker.com/_/redis" class="external-link" target="_blank">Redis</a>
|
||||
* <a href="https://hub.docker.com/_/redis" class="external-link" target="_blank">Redis</a>, и т.д.
|
||||
|
||||
и т.п.
|
||||
Используя готовые образы, очень легко **комбинировать** разные инструменты и использовать их. Например, чтобы попробовать новую базу данных. В большинстве случаев можно воспользоваться **официальными образами** и просто настроить их через переменные окружения.
|
||||
|
||||
Использование подготовленных образов значительно упрощает **комбинирование** и использование разных инструментов. Например, вы можете попытаться использовать новую базу данных. В большинстве случаев можно использовать **официальный образ** и всего лишь указать переменные окружения.
|
||||
Таким образом, во многих случаях вы можете изучить контейнеры и Docker и переиспользовать эти знания с множеством различных инструментов и компонентов.
|
||||
|
||||
Таким образом, вы можете изучить, что такое контейнеризация и Docker, и использовать полученные знания с разными инструментами и компонентами.
|
||||
Например, вы можете запустить **несколько контейнеров**: с базой данных, Python-приложением, веб-сервером с фронтендом на React и связать их через внутреннюю сеть.
|
||||
|
||||
Так, вы можете запустить одновременно **множество контейнеров** с базой данных, Python-приложением, веб-сервером, React-приложением и соединить их вместе через внутреннюю сеть.
|
||||
Все системы управления контейнерами (такие как Docker или Kubernetes) имеют интегрированные возможности для такого сетевого взаимодействия.
|
||||
|
||||
Все системы управления контейнерами (такие, как Docker или Kubernetes) имеют встроенные возможности для организации такого сетевого взаимодействия.
|
||||
## Контейнеры и процессы { #containers-and-processes }
|
||||
|
||||
## Контейнеры и процессы
|
||||
**Образ контейнера** обычно включает в свои метаданные программу или команду по умолчанию, которую следует запускать при старте **контейнера**, а также параметры, передаваемые этой программе. Это очень похоже на запуск команды в терминале.
|
||||
|
||||
Обычно **образ контейнера** содержит метаданные предустановленной программы или команду, которую следует выполнить при запуске **контейнера**. Также он может содержать параметры, передаваемые предустановленной программе. Похоже на то, как если бы вы запускали такую программу через терминал.
|
||||
Когда **контейнер** стартует, он выполняет указанную команду/программу (хотя вы можете переопределить это и запустить другую команду/программу).
|
||||
|
||||
Когда **контейнер** запущен, он будет выполнять прописанные в нём команды и программы. Но вы можете изменить его так, чтоб он выполнял другие команды и программы.
|
||||
Контейнер работает до тех пор, пока работает его **главный процесс** (команда или программа).
|
||||
|
||||
Контейнер будет работать до тех пор, пока выполняется его **главный процесс** (команда или программа).
|
||||
Обычно в контейнере есть **один процесс**, но главный процесс может запускать подпроцессы, и тогда в том же контейнере будет **несколько процессов**.
|
||||
|
||||
В контейнере обычно выполняется **только один процесс**, но от его имени можно запустить другие процессы, тогда в этом же в контейнере будет выполняться **множество процессов**.
|
||||
Нельзя иметь работающий контейнер без **хотя бы одного запущенного процесса**. Если главный процесс останавливается, контейнер останавливается.
|
||||
|
||||
Контейнер не считается запущенным, если в нём **не выполняется хотя бы один процесс**. Если главный процесс остановлен, значит и контейнер остановлен.
|
||||
## Создать Docker-образ для FastAPI { #build-a-docker-image-for-fastapi }
|
||||
|
||||
## Создать Docker-образ для FastAPI
|
||||
Итак, давайте что-нибудь соберём! 🚀
|
||||
|
||||
Что ж, давайте ужё создадим что-нибудь! 🚀
|
||||
Я покажу, как собрать **Docker-образ** для FastAPI **с нуля** на основе **официального образа Python**.
|
||||
|
||||
Я покажу Вам, как собирать **Docker-образ** для FastAPI **с нуля**, основываясь на **официальном образе Python**.
|
||||
Именно так стоит делать в **большинстве случаев**, например:
|
||||
|
||||
Такой подход сгодится для **большинства случаев**, например:
|
||||
* При использовании **Kubernetes** или похожих инструментов
|
||||
* При запуске на **Raspberry Pi**
|
||||
* При использовании облачного сервиса, который запускает для вас образ контейнера и т.п.
|
||||
|
||||
* Использование с **Kubernetes** или аналогичным инструментом
|
||||
* Запуск в **Raspberry Pi**
|
||||
* Использование в облачных сервисах, запускающих образы контейнеров для вас и т.п.
|
||||
### Зависимости пакетов { #package-requirements }
|
||||
|
||||
### Установить зависимости
|
||||
Обычно **зависимости** вашего приложения описаны в каком-то файле.
|
||||
|
||||
Обычно вашему приложению необходимы **дополнительные библиотеки**, список которых находится в отдельном файле.
|
||||
Конкретный формат зависит в основном от инструмента, которым вы **устанавливаете** эти зависимости.
|
||||
|
||||
На название и содержание такого файла влияет выбранный Вами инструмент **установки** этих библиотек (зависимостей).
|
||||
Чаще всего используется файл `requirements.txt` с именами пакетов и их версиями по одному на строку.
|
||||
|
||||
Чаще всего это простой файл `requirements.txt` с построчным перечислением библиотек и их версий.
|
||||
Разумеется, вы будете придерживаться тех же идей, что описаны здесь: [О версиях FastAPI](versions.md){.internal-link target=_blank}, чтобы задать диапазоны версий.
|
||||
|
||||
При этом Вы, для выбора версий, будете использовать те же идеи, что упомянуты на странице [О версиях FastAPI](versions.md){.internal-link target=_blank}.
|
||||
|
||||
Ваш файл `requirements.txt` может выглядеть как-то так:
|
||||
Например, ваш `requirements.txt` может выглядеть так:
|
||||
|
||||
```
|
||||
fastapi>=0.68.0,<0.69.0
|
||||
pydantic>=1.8.0,<2.0.0
|
||||
uvicorn>=0.15.0,<0.16.0
|
||||
fastapi[standard]>=0.113.0,<0.114.0
|
||||
pydantic>=2.7.0,<3.0.0
|
||||
```
|
||||
|
||||
Устанавливать зависимости проще всего с помощью `pip`:
|
||||
И обычно вы установите эти зависимости командой `pip`, например:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install -r requirements.txt
|
||||
---> 100%
|
||||
Successfully installed fastapi pydantic uvicorn
|
||||
Successfully installed fastapi pydantic
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
/// info | Информация
|
||||
|
||||
Существуют и другие инструменты управления зависимостями.
|
||||
|
||||
В этом же разделе, но позже, я покажу вам пример использования Poetry. 👇
|
||||
Существуют и другие форматы и инструменты для описания и установки зависимостей.
|
||||
|
||||
///
|
||||
|
||||
### Создать приложение **FastAPI**
|
||||
### Создать код **FastAPI** { #create-the-fastapi-code }
|
||||
|
||||
* Создайте директорию `app` и перейдите в неё.
|
||||
* Создайте пустой файл `__init__.py`.
|
||||
* Создайте файл `main.py` и заполните его:
|
||||
* Создайте файл `main.py` со следующим содержимым:
|
||||
|
||||
```Python
|
||||
from typing import Union
|
||||
@@ -167,77 +162,109 @@ def read_item(item_id: int, q: Union[str, None] = None):
|
||||
return {"item_id": item_id, "q": q}
|
||||
```
|
||||
|
||||
### Dockerfile
|
||||
### Dockerfile { #dockerfile }
|
||||
|
||||
В этой же директории создайте файл `Dockerfile` и заполните его:
|
||||
Теперь в той же директории проекта создайте файл `Dockerfile`:
|
||||
|
||||
```{ .dockerfile .annotate }
|
||||
# (1)
|
||||
# (1)!
|
||||
FROM python:3.9
|
||||
|
||||
# (2)
|
||||
# (2)!
|
||||
WORKDIR /code
|
||||
|
||||
# (3)
|
||||
# (3)!
|
||||
COPY ./requirements.txt /code/requirements.txt
|
||||
|
||||
# (4)
|
||||
# (4)!
|
||||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
||||
|
||||
# (5)
|
||||
# (5)!
|
||||
COPY ./app /code/app
|
||||
|
||||
# (6)
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
|
||||
# (6)!
|
||||
CMD ["fastapi", "run", "app/main.py", "--port", "80"]
|
||||
```
|
||||
|
||||
1. Начните с официального образа Python, который будет основой для образа приложения.
|
||||
1. Начинаем с официального базового образа Python.
|
||||
|
||||
2. Укажите, что в дальнейшем команды запускаемые в контейнере, будут выполняться в директории `/code`.
|
||||
2. Устанавливаем текущую рабочую директорию в `/code`.
|
||||
|
||||
Инструкция создаст эту директорию внутри контейнера и мы поместим в неё файл `requirements.txt` и директорию `app`.
|
||||
Здесь мы разместим файл `requirements.txt` и директорию `app`.
|
||||
|
||||
3. Скопируете файл с зависимостями из текущей директории в `/code`.
|
||||
3. Копируем файл с зависимостями в директорию `/code`.
|
||||
|
||||
Сначала копируйте **только** файл с зависимостями.
|
||||
Сначала копируйте **только** файл с зависимостями, не остальной код.
|
||||
|
||||
Этот файл **изменяется довольно редко**, Docker ищет изменения при постройке образа и если не находит, то использует **кэш**, в котором хранятся предыдущие версии сборки образа.
|
||||
Так как этот файл **меняется нечасто**, Docker определит это и использует **кэш** на этом шаге, что позволит использовать кэш и на следующем шаге.
|
||||
|
||||
4. Установите библиотеки перечисленные в файле с зависимостями.
|
||||
4. Устанавливаем зависимости из файла с требованиями.
|
||||
|
||||
Опция `--no-cache-dir` указывает `pip` не сохранять загружаемые библиотеки на локальной машине для использования их в случае повторной загрузки. В контейнере, в случае пересборки этого шага, они всё равно будут удалены.
|
||||
Опция `--no-cache-dir` указывает `pip` не сохранять загруженные пакеты локально, т.к. это нужно только если `pip` будет запускаться снова для установки тех же пакетов, а при работе с контейнерами это обычно не требуется.
|
||||
|
||||
/// note | Заметка
|
||||
|
||||
Опция `--no-cache-dir` нужна только для `pip`, она никак не влияет на Docker или контейнеры.
|
||||
`--no-cache-dir` относится только к `pip` и не имеет отношения к Docker или контейнерам.
|
||||
|
||||
///
|
||||
|
||||
Опция `--upgrade` указывает `pip` обновить библиотеки, емли они уже установлены.
|
||||
Опция `--upgrade` указывает `pip` обновлять пакеты, если они уже установлены.
|
||||
|
||||
Как и в предыдущем шаге с копированием файла, этот шаг также будет использовать **кэш Docker** в случае отсутствия изменений.
|
||||
Поскольку предыдущий шаг с копированием файла может быть обработан **кэшем Docker**, этот шаг также **использует кэш Docker**, когда это возможно.
|
||||
|
||||
Использование кэша, особенно на этом шаге, позволит вам **сэкономить** кучу времени при повторной сборке образа, так как зависимости будут сохранены в кеше, а не **загружаться и устанавливаться каждый раз**.
|
||||
Использование кэша на этом шаге **сэкономит** вам много **времени** при повторных сборках образа во время разработки, вместо того чтобы **загружать и устанавливать** все зависимости **каждый раз**.
|
||||
|
||||
5. Скопируйте директорию `./app` внутрь директории `/code` (в контейнере).
|
||||
5. Копируем директорию `./app` внутрь директории `/code`.
|
||||
|
||||
Так как в этой директории расположен код, который **часто изменяется**, то использование **кэша** на этом шаге будет наименее эффективно, а значит лучше поместить этот шаг **ближе к концу** `Dockerfile`, дабы не терять выгоду от оптимизации предыдущих шагов.
|
||||
Так как здесь весь код, который **меняется чаще всего**, кэш Docker **вряд ли** будет использоваться для этого шагa или **последующих шагов**.
|
||||
|
||||
6. Укажите **команду**, запускающую сервер `uvicorn`.
|
||||
Поэтому важно разместить этот шаг **ближе к концу** `Dockerfile`, чтобы оптимизировать время сборки образа контейнера.
|
||||
|
||||
`CMD` принимает список строк, разделённых запятыми, но при выполнении объединит их через пробел, собрав из них одну команду, которую вы могли бы написать в терминале.
|
||||
6. Указываем **команду** для запуска `fastapi run`, под капотом используется Uvicorn.
|
||||
|
||||
Эта команда будет выполнена в **текущей рабочей директории**, а именно в директории `/code`, которая указана в команде `WORKDIR /code`.
|
||||
`CMD` принимает список строк, каждая из которых — это то, что вы бы ввели в командной строке, разделяя пробелами.
|
||||
|
||||
Так как команда выполняется внутри директории `/code`, в которую мы поместили папку `./app` с приложением, то **Uvicorn** сможет найти и **импортировать** объект `app` из файла `app.main`.
|
||||
Эта команда будет выполнена из **текущей рабочей директории**, той самой `/code`, которую вы задали выше `WORKDIR /code`.
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Если ткнёте на кружок с плюсом, то увидите пояснения. 👆
|
||||
Посмотрите, что делает каждая строка, кликнув по номеру рядом со строкой. 👆
|
||||
|
||||
///
|
||||
|
||||
На данном этапе структура проекта должны выглядеть так:
|
||||
/// warning | Предупреждение
|
||||
|
||||
Всегда используйте **exec-форму** инструкции `CMD`, как описано ниже.
|
||||
|
||||
///
|
||||
|
||||
#### Используйте `CMD` — exec-форма { #use-cmd-exec-form }
|
||||
|
||||
Инструкцию Docker <a href="https://docs.docker.com/reference/dockerfile/#cmd" class="external-link" target="_blank">`CMD`</a> можно писать в двух формах:
|
||||
|
||||
✅ **Exec**-форма:
|
||||
|
||||
```Dockerfile
|
||||
# ✅ Делайте так
|
||||
CMD ["fastapi", "run", "app/main.py", "--port", "80"]
|
||||
```
|
||||
|
||||
⛔️ **Shell**-форма:
|
||||
|
||||
```Dockerfile
|
||||
# ⛔️ Не делайте так
|
||||
CMD fastapi run app/main.py --port 80
|
||||
```
|
||||
|
||||
Обязательно используйте **exec**-форму, чтобы FastAPI мог корректно завершаться и чтобы срабатывали [события lifespan](../advanced/events.md){.internal-link target=_blank}.
|
||||
|
||||
Подробнее об этом читайте в <a href="https://docs.docker.com/reference/dockerfile/#shell-and-exec-form" class="external-link" target="_blank">документации Docker о shell- и exec-формах</a>.
|
||||
|
||||
Это особенно заметно при использовании `docker compose`. См. раздел FAQ Docker Compose с техническими подробностями: <a href="https://docs.docker.com/compose/faq/#why-do-my-services-take-10-seconds-to-recreate-or-stop" class="external-link" target="_blank">Почему мои сервисы пересоздаются или останавливаются 10 секунд?</a>.
|
||||
|
||||
#### Структура директорий { #directory-structure }
|
||||
|
||||
Теперь у вас должна быть такая структура:
|
||||
|
||||
```
|
||||
.
|
||||
@@ -248,53 +275,52 @@ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
|
||||
└── requirements.txt
|
||||
```
|
||||
|
||||
#### Использование прокси-сервера
|
||||
#### За прокси-сервером TLS терминации { #behind-a-tls-termination-proxy }
|
||||
|
||||
Если вы запускаете контейнер за прокси-сервером завершения TLS (балансирующего нагрузку), таким как Nginx или Traefik, добавьте опцию `--proxy-headers`, которая укажет Uvicorn, что он работает позади прокси-сервера и может доверять заголовкам отправляемым им.
|
||||
Если вы запускаете контейнер за прокси-сервером завершения TLS (балансировщиком нагрузки), таким как Nginx или Traefik, добавьте опцию `--proxy-headers`. Это сообщит Uvicorn (через FastAPI CLI), что приложение работает за HTTPS и можно доверять соответствующим заголовкам.
|
||||
|
||||
```Dockerfile
|
||||
CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "80"]
|
||||
CMD ["fastapi", "run", "app/main.py", "--proxy-headers", "--port", "80"]
|
||||
```
|
||||
|
||||
#### Кэш Docker'а
|
||||
#### Кэш Docker { #docker-cache }
|
||||
|
||||
В нашем `Dockerfile` использована полезная хитрость, когда сначала копируется **только файл с зависимостями**, а не вся папка с кодом приложения.
|
||||
В этом `Dockerfile` есть важная хитрость: мы сначала копируем **только файл с зависимостями**, а не весь код. Вот зачем.
|
||||
|
||||
```Dockerfile
|
||||
COPY ./requirements.txt /code/requirements.txt
|
||||
```
|
||||
|
||||
Docker и подобные ему инструменты **создают** образы контейнеров **пошагово**, добавляя **один слой над другим**, начиная с первой строки `Dockerfile` и добавляя файлы, создаваемые при выполнении каждой инструкции из `Dockerfile`.
|
||||
Docker и подобные инструменты **строят** образы контейнеров **инкрементально**, добавляя **слой за слоем**, начиная с первой строки `Dockerfile` и добавляя любые файлы, создаваемые каждой инструкцией `Dockerfile`.
|
||||
|
||||
При создании образа используется **внутренний кэш** и если в файлах нет изменений с момента последней сборки образа, то будет **переиспользован** ранее созданный слой образа, а не повторное копирование файлов и создание слоя с нуля.
|
||||
Заметьте, что так как слой следующего шага зависит от слоя предыдущего, то изменения внесённые в промежуточный слой, также повлияют на последующие.
|
||||
Docker и подобные инструменты также используют **внутренний кэш** при сборке образа: если файл не изменился с момента предыдущей сборки, будет **переиспользован слой**, созданный в прошлый раз, вместо повторного копирования файла и создания нового слоя с нуля.
|
||||
|
||||
Избегание копирования файлов не обязательно улучшит ситуацию, но использование кэша на одном шаге, позволит **использовать кэш и на следующих шагах**. Например, можно использовать кэш при установке зависимостей:
|
||||
Само по себе избегание копирования всех файлов не всегда даёт много, но благодаря использованию кэша на этом шаге Docker сможет **использовать кэш и на следующем шаге**. Например, на шаге установки зависимостей:
|
||||
|
||||
```Dockerfile
|
||||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
||||
```
|
||||
|
||||
Файл со списком зависимостей **изменяется довольно редко**. Так что выполнив команду копирования только этого файла, Docker сможет **использовать кэш** на этом шаге.
|
||||
Файл с зависимостями **меняется нечасто**. Поэтому, копируя только его, Docker сможет **использовать кэш** для этого шага.
|
||||
|
||||
А затем **использовать кэш и на следующем шаге**, загружающем и устанавливающем зависимости. И вот тут-то мы и **сэкономим много времени**. ✨ ...а не будем томиться в тягостном ожидании. 😪😆
|
||||
А затем Docker сможет **использовать кэш и на следующем шаге**, где скачиваются и устанавливаются зависимости. Здесь мы как раз **экономим много времени**. ✨ ...и не скучаем в ожидании. 😪😆
|
||||
|
||||
Для загрузки и установки необходимых библиотек **может понадобиться несколько минут**, но использование **кэша** занимает несколько **секунд** максимум.
|
||||
Скачивание и установка зависимостей **может занять минуты**, но использование **кэша** — **секунды**.
|
||||
|
||||
И так как во время разработки вы будете часто пересобирать контейнер для проверки работоспособности внесённых изменений, то сэкономленные минуты сложатся в часы, а то и дни.
|
||||
Поскольку во время разработки вы будете пересобирать образ снова и снова, чтобы проверить изменения в коде, суммарно это сэкономит немало времени.
|
||||
|
||||
Так как папка с кодом приложения **изменяется чаще всего**, то мы расположили её в конце `Dockerfile`, ведь после внесённых в код изменений кэш не будет использован на этом и следующих шагах.
|
||||
Затем, ближе к концу `Dockerfile`, мы копируем весь код. Так как он **меняется чаще всего**, мы ставим этот шаг в конец, потому что почти всегда всё, что после него, уже не сможет использовать кэш.
|
||||
|
||||
```Dockerfile
|
||||
COPY ./app /code/app
|
||||
```
|
||||
|
||||
### Создать Docker-образ
|
||||
### Собрать Docker-образ { #build-the-docker-image }
|
||||
|
||||
Теперь, когда все файлы на своих местах, давайте создадим образ контейнера.
|
||||
Теперь, когда все файлы на месте, соберём образ контейнера.
|
||||
|
||||
* Перейдите в директорию проекта (в ту, где расположены `Dockerfile` и папка `app` с приложением).
|
||||
* Создай образ приложения FastAPI:
|
||||
* Перейдите в директорию проекта (где ваш `Dockerfile` и директория `app`).
|
||||
* Соберите образ FastAPI:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -308,15 +334,15 @@ $ docker build -t myimage .
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Обратите внимание, что в конце написана точка - `.`, это то же самое что и `./`, тем самым мы указываем Docker директорию, из которой нужно выполнять сборку образа контейнера.
|
||||
Обратите внимание на точку `.` в конце — это то же самое, что `./`. Так мы указываем Docker, из какой директории собирать образ контейнера.
|
||||
|
||||
В данном случае это та же самая директория (`.`).
|
||||
В данном случае это текущая директория (`.`).
|
||||
|
||||
///
|
||||
|
||||
### Запуск Docker-контейнера
|
||||
### Запустить Docker-контейнер { #start-the-docker-container }
|
||||
|
||||
* Запустите контейнер, основанный на вашем образе:
|
||||
* Запустите контейнер на основе вашего образа:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -326,35 +352,35 @@ $ docker run -d --name mycontainer -p 80:80 myimage
|
||||
|
||||
</div>
|
||||
|
||||
## Проверка
|
||||
## Проверка { #check-it }
|
||||
|
||||
Вы можете проверить, что Ваш Docker-контейнер работает перейдя по ссылке: <a href="http://192.168.99.100/items/5?q=somequery" class="external-link" target="_blank">http://192.168.99.100/items/5?q=somequery</a> или <a href="http://127.0.0.1/items/5?q=somequery" class="external-link" target="_blank">http://127.0.0.1/items/5?q=somequery</a> (или похожей, которую использует Ваш Docker-хост).
|
||||
Проверьте работу по адресу вашего Docker-хоста, например: <a href="http://192.168.99.100/items/5?q=somequery" class="external-link" target="_blank">http://192.168.99.100/items/5?q=somequery</a> или <a href="http://127.0.0.1/items/5?q=somequery" class="external-link" target="_blank">http://127.0.0.1/items/5?q=somequery</a> (или аналогичный URL вашего Docker-хоста).
|
||||
|
||||
Там вы увидите:
|
||||
Вы увидите что-то вроде:
|
||||
|
||||
```JSON
|
||||
{"item_id": 5, "q": "somequery"}
|
||||
```
|
||||
|
||||
## Интерактивная документация API
|
||||
## Интерактивная документация API { #interactive-api-docs }
|
||||
|
||||
Теперь перейдите по ссылке <a href="http://192.168.99.100/docs" class="external-link" target="_blank">http://192.168.99.100/docs</a> или <a href="http://127.0.0.1/docs" class="external-link" target="_blank">http://127.0.0.1/docs</a> (или похожей, которую использует Ваш Docker-хост).
|
||||
Теперь зайдите на <a href="http://192.168.99.100/docs" class="external-link" target="_blank">http://192.168.99.100/docs</a> или <a href="http://127.0.0.1/docs" class="external-link" target="_blank">http://127.0.0.1/docs</a> (или аналогичный URL вашего Docker-хоста).
|
||||
|
||||
Здесь вы увидите автоматическую интерактивную документацию API (предоставляемую <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>):
|
||||
Вы увидите автоматическую интерактивную документацию API (на базе <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>):
|
||||
|
||||

|
||||
|
||||
## Альтернативная документация API
|
||||
## Альтернативная документация API { #alternative-api-docs }
|
||||
|
||||
Также вы можете перейти по ссылке <a href="http://192.168.99.100/redoc" class="external-link" target="_blank">http://192.168.99.100/redoc</a> or <a href="http://127.0.0.1/redoc" class="external-link" target="_blank">http://127.0.0.1/redoc</a> (или похожей, которую использует Ваш Docker-хост).
|
||||
Также можно открыть <a href="http://192.168.99.100/redoc" class="external-link" target="_blank">http://192.168.99.100/redoc</a> или <a href="http://127.0.0.1/redoc" class="external-link" target="_blank">http://127.0.0.1/redoc</a> (или аналогичный URL вашего Docker-хоста).
|
||||
|
||||
Здесь вы увидите альтернативную автоматическую документацию API (предоставляемую <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>):
|
||||
Вы увидите альтернативную автоматическую документацию (на базе <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>):
|
||||
|
||||

|
||||
|
||||
## Создание Docker-образа на основе однофайлового приложения FastAPI
|
||||
## Собрать Docker-образ для однофайлового FastAPI { #build-a-docker-image-with-a-single-file-fastapi }
|
||||
|
||||
Если ваше приложение FastAPI помещено в один файл, например, `main.py` и структура Ваших файлов похожа на эту:
|
||||
Если ваше приложение FastAPI — один файл, например `main.py` без директории `./app`, структура файлов может быть такой:
|
||||
|
||||
```
|
||||
.
|
||||
@@ -363,7 +389,7 @@ $ docker run -d --name mycontainer -p 80:80 myimage
|
||||
└── requirements.txt
|
||||
```
|
||||
|
||||
Вам нужно изменить в `Dockerfile` соответствующие пути копирования файлов:
|
||||
Тогда в `Dockerfile` нужно изменить пути копирования:
|
||||
|
||||
```{ .dockerfile .annotate hl_lines="10 13" }
|
||||
FROM python:3.9
|
||||
@@ -374,360 +400,221 @@ COPY ./requirements.txt /code/requirements.txt
|
||||
|
||||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
||||
|
||||
# (1)
|
||||
# (1)!
|
||||
COPY ./main.py /code/
|
||||
|
||||
# (2)
|
||||
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"]
|
||||
# (2)!
|
||||
CMD ["fastapi", "run", "main.py", "--port", "80"]
|
||||
```
|
||||
|
||||
1. Скопируйте непосредственно файл `main.py` в директорию `/code` (не указывайте `./app`).
|
||||
1. Копируем файл `main.py` напрямую в `/code` (без директории `./app`).
|
||||
|
||||
2. При запуске Uvicorn укажите ему, что объект `app` нужно импортировать из файла `main` (вместо импортирования из `app.main`).
|
||||
2. Используем `fastapi run` для запуска приложения из одного файла `main.py`.
|
||||
|
||||
Настройте Uvicorn на использование `main` вместо `app.main` для импорта объекта `app`.
|
||||
Когда вы передаёте файл в `fastapi run`, он автоматически определит, что это одиночный файл, а не часть пакета, и поймёт, как его импортировать и запустить ваше FastAPI-приложение. 😎
|
||||
|
||||
## Концепции развёртывания
|
||||
## Концепции развертывания { #deployment-concepts }
|
||||
|
||||
Давайте вспомним о [Концепциях развёртывания](concepts.md){.internal-link target=_blank} и применим их к контейнерам.
|
||||
Ещё раз рассмотрим [концепции развертывания](concepts.md){.internal-link target=_blank} применительно к контейнерам.
|
||||
|
||||
Контейнеры - это, в основном, инструмент упрощающий **сборку и развёртывание** приложения и они не обязывают к применению какой-то определённой **концепции развёртывания**, а значит мы можем выбирать нужную стратегию.
|
||||
Контейнеры главным образом упрощают **сборку и развёртывание** приложения, но не навязывают конкретный подход к этим **концепциям развертывания**, и существует несколько стратегий.
|
||||
|
||||
**Хорошая новость** в том, что независимо от выбранной стратегии, мы всё равно можем покрыть все концепции развёртывания. 🎉
|
||||
**Хорошая новость** в том, что при любой стратегии есть способ охватить все концепции развертывания. 🎉
|
||||
|
||||
Рассмотрим эти **концепции развёртывания** применительно к контейнерам:
|
||||
Рассмотрим эти **концепции развертывания** в терминах контейнеров:
|
||||
|
||||
* Использование более безопасного протокола HTTPS
|
||||
* Настройки запуска приложения
|
||||
* Перезагрузка приложения
|
||||
* Запуск нескольких экземпляров приложения
|
||||
* Управление памятью
|
||||
* Использование перечисленных функций перед запуском приложения
|
||||
* HTTPS
|
||||
* Запуск при старте
|
||||
* Перезапуски
|
||||
* Репликация (количество запущенных процессов)
|
||||
* Память
|
||||
* Предварительные шаги перед запуском
|
||||
|
||||
## Использование более безопасного протокола HTTPS
|
||||
## HTTPS { #https }
|
||||
|
||||
Если мы определимся, что **образ контейнера** будет содержать только приложение FastAPI, то работу с HTTPS можно организовать **снаружи** контейнера при помощи другого инструмента.
|
||||
Если мы рассматриваем только **образ контейнера** для приложения FastAPI (и далее запущенный **контейнер**), то HTTPS обычно обрабатывается **внешним** инструментом.
|
||||
|
||||
Это может быть другой контейнер, в котором есть, например, <a href="https://traefik.io/" class="external-link" target="_blank">Traefik</a>, работающий с **HTTPS** и **самостоятельно** обновляющий **сертификаты**.
|
||||
Это может быть другой контейнер, например с <a href="https://traefik.io/" class="external-link" target="_blank">Traefik</a>, который берёт на себя **HTTPS** и **автоматическое** получение **сертификатов**.
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Traefik совместим с Docker, Kubernetes и им подобными инструментами. Он очень прост в установке и настройке использования HTTPS для Ваших контейнеров.
|
||||
У Traefik есть интеграции с Docker, Kubernetes и другими, поэтому очень легко настроить и сконфигурировать HTTPS для ваших контейнеров.
|
||||
|
||||
///
|
||||
|
||||
В качестве альтернативы, работу с HTTPS можно доверить облачному провайдеру, если он предоставляет такую услугу.
|
||||
В качестве альтернативы HTTPS может быть реализован как сервис облачного провайдера (при этом приложение всё равно работает в контейнере).
|
||||
|
||||
## Настройки запуска и перезагрузки приложения
|
||||
## Запуск при старте и перезапуски { #running-on-startup-and-restarts }
|
||||
|
||||
Обычно **запуском контейнера с приложением** занимается какой-то отдельный инструмент.
|
||||
Обычно есть другой инструмент, отвечающий за **запуск и работу** вашего контейнера.
|
||||
|
||||
Это может быть сам **Docker**, **Docker Compose**, **Kubernetes**, **облачный провайдер** и т.п.
|
||||
Это может быть сам **Docker**, **Docker Compose**, **Kubernetes**, **облачный сервис** и т.п.
|
||||
|
||||
В большинстве случаев это простейшие настройки запуска и перезагрузки приложения (при падении). Например, команде запуска Docker-контейнера можно добавить опцию `--restart`.
|
||||
В большинстве (или во всех) случаев есть простая опция, чтобы включить запуск контейнера при старте системы и перезапуски при сбоях. Например, в Docker это опция командной строки `--restart`.
|
||||
|
||||
Управление запуском и перезагрузкой приложений без использования контейнеров - весьма затруднительно. Но при **работе с контейнерами** - это всего лишь функционал доступный по умолчанию. ✨
|
||||
Без контейнеров обеспечить запуск при старте и перезапуски может быть сложно. Но при **работе с контейнерами** в большинстве случаев этот функционал доступен по умолчанию. ✨
|
||||
|
||||
## Запуск нескольких экземпляров приложения - Указание количества процессов
|
||||
## Репликация — количество процессов { #replication-number-of-processes }
|
||||
|
||||
Если у вас есть <abbr title="Несколько серверов настроенных для совместной работы.">кластер</abbr> машин под управлением **Kubernetes**, Docker Swarm Mode, Nomad или аналогичной сложной системой оркестрации контейнеров, скорее всего, вместо использования менеджера процессов (типа Gunicorn и его воркеры) в каждом контейнере, вы захотите **управлять количеством запущенных экземпляров приложения** на **уровне кластера**.
|
||||
Если у вас есть <abbr title="Группа машин, настроенных так, чтобы быть соединенными и работать вместе определенным образом.">кластер</abbr> машин с **Kubernetes**, Docker Swarm Mode, Nomad или другой похожей системой для управления распределёнными контейнерами на нескольких машинах, скорее всего вы будете **управлять репликацией** на **уровне кластера**, а не использовать **менеджер процессов** (например, Uvicorn с воркерами) в каждом контейнере.
|
||||
|
||||
В любую из этих систем управления контейнерами обычно встроен способ управления **количеством запущенных контейнеров** для распределения **нагрузки** от входящих запросов на **уровне кластера**.
|
||||
Одна из таких систем управления распределёнными контейнерами, как Kubernetes, обычно имеет встроенный способ управлять **репликацией контейнеров**, поддерживая **балансировку нагрузки** для входящих запросов — всё это на **уровне кластера**.
|
||||
|
||||
В такой ситуации Вы, вероятно, захотите создать **образ Docker**, как [описано выше](#dockerfile), с установленными зависимостями и запускающий **один процесс Uvicorn** вместо того, чтобы запускать Gunicorn управляющий несколькими воркерами Uvicorn.
|
||||
В таких случаях вы, скорее всего, захотите собрать **Docker-образ с нуля**, как [описано выше](#dockerfile), установить зависимости и запускать **один процесс Uvicorn** вместо множества воркеров Uvicorn.
|
||||
|
||||
### Балансировщик нагрузки
|
||||
### Балансировщик нагрузки { #load-balancer }
|
||||
|
||||
Обычно при использовании контейнеров один компонент **прослушивает главный порт**. Это может быть контейнер содержащий **прокси-сервер завершения работы TLS** для работы с **HTTPS** или что-то подобное.
|
||||
При использовании контейнеров обычно есть компонент, **слушающий главный порт**. Это может быть другой контейнер — **прокси завершения TLS** для обработки **HTTPS** или похожий инструмент.
|
||||
|
||||
Поскольку этот компонент **принимает запросы** и равномерно **распределяет** их между компонентами, его также называют **балансировщиком нагрузки**.
|
||||
Поскольку этот компонент принимает **нагрузку** запросов и распределяет её между воркерами **сбалансированно**, его часто называют **балансировщиком нагрузки**.
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
**Прокси-сервер завершения работы TLS** одновременно может быть **балансировщиком нагрузки**.
|
||||
Тот же компонент **прокси завершения TLS**, который обрабатывает HTTPS, скорее всего также будет **балансировщиком нагрузки**.
|
||||
|
||||
///
|
||||
|
||||
Система оркестрации, которую вы используете для запуска и управления контейнерами, имеет встроенный инструмент **сетевого взаимодействия** (например, для передачи HTTP-запросов) между контейнерами с Вашими приложениями и **балансировщиком нагрузки** (который также может быть **прокси-сервером**).
|
||||
При работе с контейнерами система, которую вы используете для запуска и управления ими, уже имеет внутренние средства для передачи **сетевого взаимодействия** (например, HTTP-запросов) от **балансировщика нагрузки** (который также может быть **прокси завершения TLS**) к контейнеру(-ам) с вашим приложением.
|
||||
|
||||
### Один балансировщик - Множество контейнеров
|
||||
### Один балансировщик — несколько контейнеров-воркеров { #one-load-balancer-multiple-worker-containers }
|
||||
|
||||
При работе с **Kubernetes** или аналогичными системами оркестрации использование их внутренней сети позволяет иметь один **балансировщик нагрузки**, который прослушивает **главный** порт и передаёт запросы **множеству запущенных контейнеров** с Вашими приложениями.
|
||||
При работе с **Kubernetes** или похожими системами управления распределёнными контейнерами их внутренние механизмы сети позволяют одному **балансировщику нагрузки**, слушающему главный **порт**, передавать запросы в **несколько контейнеров**, где запущено ваше приложение.
|
||||
|
||||
В каждом из контейнеров обычно работает **только один процесс** (например, процесс Uvicorn управляющий Вашим приложением FastAPI). Контейнеры могут быть **идентичными**, запущенными на основе одного и того же образа, но у каждого будут свои отдельные процесс, память и т.п. Таким образом мы получаем преимущества **распараллеливания** работы по **разным ядрам** процессора или даже **разным машинам**.
|
||||
Каждый такой контейнер с вашим приложением обычно имеет **только один процесс** (например, процесс Uvicorn с вашим приложением FastAPI). Все они — **одинаковые контейнеры**, запускающие одно и то же, но у каждого свой процесс, память и т.п. Так вы используете **параллелизм** по **разным ядрам** CPU или даже **разным машинам**.
|
||||
|
||||
Система управления контейнерами с **балансировщиком нагрузки** будет **распределять запросы** к контейнерам с приложениями **по очереди**. То есть каждый запрос будет обработан одним из множества **одинаковых контейнеров** с одним и тем же приложением.
|
||||
Система распределённых контейнеров с **балансировщиком нагрузки** будет **распределять запросы** между контейнерами с вашим приложением **по очереди**. То есть каждый запрос может обрабатываться одним из нескольких **реплицированных контейнеров**.
|
||||
|
||||
**Балансировщик нагрузки** может обрабатывать запросы к *разным* приложениям, расположенным в вашем кластере (например, если у них разные домены или префиксы пути) и передавать запросы нужному контейнеру с требуемым приложением.
|
||||
Обычно такой **балансировщик нагрузки** может также обрабатывать запросы к *другим* приложениям в вашем кластере (например, к другому домену или под другим префиксом пути URL) и направлять их к нужным контейнерам этого *другого* приложения.
|
||||
|
||||
### Один процесс на контейнер
|
||||
### Один процесс на контейнер { #one-process-per-container }
|
||||
|
||||
В этом варианте **в одном контейнере будет запущен только один процесс (Uvicorn)**, а управление изменением количества запущенных копий приложения происходит на уровне кластера.
|
||||
В таком сценарии, скорее всего, вы захотите иметь **один (Uvicorn) процесс на контейнер**, так как репликация уже управляется на уровне кластера.
|
||||
|
||||
Здесь **не нужен** менеджер процессов типа Gunicorn, управляющий процессами Uvicorn, или же Uvicorn, управляющий другими процессами Uvicorn. Достаточно **только одного процесса Uvicorn** на контейнер (но запуск нескольких процессов не запрещён).
|
||||
Поэтому в контейнере **не нужно** поднимать несколько воркеров, например через опцию командной строки `--workers`. Нужен **один процесс Uvicorn** на контейнер (но, возможно, несколько контейнеров).
|
||||
|
||||
Использование менеджера процессов (Gunicorn или Uvicorn) внутри контейнера только добавляет **излишнее усложнение**, так как управление следует осуществлять системой оркестрации.
|
||||
Наличие отдельного менеджера процессов внутри контейнера (как при нескольких воркерах) только добавит **лишнюю сложность**, которую, вероятно, уже берёт на себя ваша кластерная система.
|
||||
|
||||
### Множество процессов внутри контейнера для особых случаев
|
||||
### Контейнеры с несколькими процессами и особые случаи { #containers-with-multiple-processes-and-special-cases }
|
||||
|
||||
Безусловно, бывают **особые случаи**, когда может понадобиться внутри контейнера запускать **менеджер процессов Gunicorn**, управляющий несколькими **процессами Uvicorn**.
|
||||
Конечно, есть **особые случаи**, когда может понадобиться **контейнер** с несколькими **воркерами Uvicorn** внутри.
|
||||
|
||||
Для таких случаев вы можете использовать **официальный Docker-образ** (прим. пер: - *здесь и далее на этой странице, если вы встретите сочетание "официальный Docker-образ" без уточнений, то автор имеет в виду именно предоставляемый им образ*), где в качестве менеджера процессов используется **Gunicorn**, запускающий несколько **процессов Uvicorn** и некоторые настройки по умолчанию, автоматически устанавливающие количество запущенных процессов в зависимости от количества ядер вашего процессора. Я расскажу вам об этом подробнее тут: [Официальный Docker-образ со встроенными Gunicorn и Uvicorn](#docker-gunicorn-uvicorn).
|
||||
В таких случаях вы можете использовать опцию командной строки `--workers`, чтобы указать нужное количество воркеров:
|
||||
|
||||
Некоторые примеры подобных случаев:
|
||||
```{ .dockerfile .annotate }
|
||||
FROM python:3.9
|
||||
|
||||
#### Простое приложение
|
||||
WORKDIR /code
|
||||
|
||||
Вы можете использовать менеджер процессов внутри контейнера, если ваше приложение **настолько простое**, что у вас нет необходимости (по крайней мере, пока нет) в тщательных настройках количества процессов и вам достаточно имеющихся настроек по умолчанию (если используется официальный Docker-образ) для запуска приложения **только на одном сервере**, а не в кластере.
|
||||
COPY ./requirements.txt /code/requirements.txt
|
||||
|
||||
#### Docker Compose
|
||||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
||||
|
||||
С помощью **Docker Compose** можно разворачивать несколько контейнеров на **одном сервере** (не кластере), но при это у вас не будет простого способа управления количеством запущенных контейнеров с одновременным сохранением общей сети и **балансировки нагрузки**.
|
||||
COPY ./app /code/app
|
||||
|
||||
В этом случае можно использовать **менеджер процессов**, управляющий **несколькими процессами**, внутри **одного контейнера**.
|
||||
# (1)!
|
||||
CMD ["fastapi", "run", "app/main.py", "--port", "80", "--workers", "4"]
|
||||
```
|
||||
|
||||
#### Prometheus и прочие причины
|
||||
1. Здесь мы используем опцию `--workers`, чтобы установить число воркеров равным 4.
|
||||
|
||||
У вас могут быть и **другие причины**, когда использование **множества процессов** внутри **одного контейнера** будет проще, нежели запуск **нескольких контейнеров** с **единственным процессом** в каждом из них.
|
||||
Примеры, когда это может быть уместно:
|
||||
|
||||
Например (в зависимости от конфигурации), у вас могут быть инструменты подобные экспортёру Prometheus, которые должны иметь доступ к **каждому запросу** приходящему в контейнер.
|
||||
#### Простое приложение { #a-simple-app }
|
||||
|
||||
Если у вас будет **несколько контейнеров**, то Prometheus, по умолчанию, **при сборе метрик** получит их **только с одного контейнера**, который обрабатывает конкретный запрос, вместо **сбора метрик** со всех работающих контейнеров.
|
||||
Вам может понадобиться менеджер процессов в контейнере, если приложение **достаточно простое**, чтобы запускаться на **одном сервере**, а не в кластере.
|
||||
|
||||
В таком случае может быть проще иметь **один контейнер** со **множеством процессов**, с нужным инструментом (таким как экспортёр Prometheus) в этом же контейнере и собирающем метрики со всех внутренних процессов этого контейнера.
|
||||
#### Docker Compose { #docker-compose }
|
||||
|
||||
Вы можете развёртывать на **одном сервере** (не кластере) с **Docker Compose**, и у вас не будет простого способа управлять репликацией контейнеров (в Docker Compose), сохраняя общую сеть и **балансировку нагрузки**.
|
||||
|
||||
Тогда вы можете захотеть **один контейнер** с **менеджером процессов**, который запускает **несколько воркеров** внутри.
|
||||
|
||||
---
|
||||
|
||||
Самое главное - **ни одно** из перечисленных правил не является **высеченным на камне** и вы не обязаны слепо их повторять. вы можете использовать эти идеи при **рассмотрении вашего конкретного случая** и самостоятельно решать, какая из концепции подходит лучше:
|
||||
Главное — **ни одно** из этих правил не является **строго обязательным**. Используйте эти идеи, чтобы **оценить свой конкретный случай** и решить, какой подход лучше для вашей системы, учитывая:
|
||||
|
||||
* Использование более безопасного протокола HTTPS
|
||||
* Настройки запуска приложения
|
||||
* Перезагрузка приложения
|
||||
* Запуск нескольких экземпляров приложения
|
||||
* Управление памятью
|
||||
* Использование перечисленных функций перед запуском приложения
|
||||
* Безопасность — HTTPS
|
||||
* Запуск при старте
|
||||
* Перезапуски
|
||||
* Репликацию (количество запущенных процессов)
|
||||
* Память
|
||||
* Предварительные шаги перед запуском
|
||||
|
||||
## Управление памятью
|
||||
## Память { #memory }
|
||||
|
||||
При **запуске одного процесса на контейнер** вы получаете относительно понятный, стабильный и ограниченный объём памяти, потребляемый одним контейнером.
|
||||
Если вы запускаете **один процесс на контейнер**, у каждого контейнера будет более-менее чётко определённый, стабильный и ограниченный объём потребляемой памяти (контейнеров может быть несколько при репликации).
|
||||
|
||||
Вы можете установить аналогичные ограничения по памяти при конфигурировании своей системы управления контейнерами (например, **Kubernetes**). Таким образом система сможет **изменять количество контейнеров** на **доступных ей машинах** приводя в соответствие количество памяти нужной контейнерам с количеством памяти доступной в кластере (наборе доступных машин).
|
||||
Затем вы можете задать такие же лимиты и требования по памяти в конфигурации вашей системы управления контейнерами (например, в **Kubernetes**). Так система сможет **реплицировать контейнеры** на **доступных машинах**, учитывая объём необходимой памяти и доступной памяти в машинах кластера.
|
||||
|
||||
Если у вас **простенькое** приложение, вероятно у вас не будет **необходимости** устанавливать жёсткие ограничения на выделяемую ему память. Но если приложение **использует много памяти** (например, оно использует модели **машинного обучения**), вам следует проверить, как много памяти ему требуется и отрегулировать **количество контейнеров** запущенных на **каждой машине** (может быть даже добавить машин в кластер).
|
||||
Если приложение **простое**, это, вероятно, **не будет проблемой**, и жёсткие лимиты памяти можно не указывать. Но если вы **используете много памяти** (например, с моделями **Машинного обучения**), проверьте, сколько памяти потребляется, и отрегулируйте **число контейнеров** на **каждой машине** (и, возможно, добавьте машины в кластер).
|
||||
|
||||
Если вы запускаете **несколько процессов в контейнере**, то должны быть уверены, что эти процессы не **займут памяти больше**, чем доступно для контейнера.
|
||||
Если вы запускаете **несколько процессов в контейнере**, нужно убедиться, что их суммарное потребление **не превысит доступную память**.
|
||||
|
||||
## Подготовительные шаги при запуске контейнеров
|
||||
## Предварительные шаги перед запуском и контейнеры { #previous-steps-before-starting-and-containers }
|
||||
|
||||
Есть два основных подхода, которые вы можете использовать при запуске контейнеров (Docker, Kubernetes и т.п.).
|
||||
Если вы используете контейнеры (например, Docker, Kubernetes), есть два основных подхода.
|
||||
|
||||
### Множество контейнеров
|
||||
### Несколько контейнеров { #multiple-containers }
|
||||
|
||||
Когда вы запускаете **множество контейнеров**, в каждом из которых работает **только один процесс** (например, в кластере **Kubernetes**), может возникнуть необходимость иметь **отдельный контейнер**, который осуществит **предварительные шаги перед запуском** остальных контейнеров (например, применяет миграции к базе данных).
|
||||
Если у вас **несколько контейнеров**, и, вероятно, каждый запускает **один процесс** (например, в кластере **Kubernetes**), то вы, скорее всего, захотите иметь **отдельный контейнер**, выполняющий **предварительные шаги** в одном контейнере и одном процессе **до** запуска реплицированных контейнеров-воркеров.
|
||||
|
||||
/// info | Информация
|
||||
|
||||
При использовании Kubernetes, это может быть <a href="https://kubernetes.io/docs/concepts/workloads/pods/init-containers/" class="external-link" target="_blank">Инициализирующий контейнер</a>.
|
||||
Если вы используете Kubernetes, это, вероятно, будет <a href="https://kubernetes.io/docs/concepts/workloads/pods/init-containers/" class="external-link" target="_blank">Init Container</a>.
|
||||
|
||||
///
|
||||
|
||||
При отсутствии такой необходимости (допустим, не нужно применять миграции к базе данных, а только проверить, что она готова принимать соединения), вы можете проводить такую проверку в каждом контейнере перед запуском его основного процесса и запускать все контейнеры **одновременно**.
|
||||
Если в вашем случае нет проблемы с тем, чтобы выполнять эти предварительные шаги **многократно и параллельно** (например, вы не запускаете миграции БД, а только проверяете готовность БД), вы можете просто выполнить их в каждом контейнере прямо перед стартом основного процесса.
|
||||
|
||||
### Только один контейнер
|
||||
### Один контейнер { #single-container }
|
||||
|
||||
Если у вас несложное приложение для работы которого достаточно **одного контейнера**, но в котором работает **несколько процессов** (или один процесс), то прохождение предварительных шагов можно осуществить в этом же контейнере до запуска основного процесса. Официальный Docker-образ поддерживает такие действия.
|
||||
Если у вас простая схема с **одним контейнером**, который затем запускает несколько **воркеров** (или один процесс), можно выполнить подготовительные шаги в этом же контейнере непосредственно перед запуском процесса с приложением.
|
||||
|
||||
## Официальный Docker-образ с Gunicorn и Uvicorn
|
||||
### Базовый Docker-образ { #base-docker-image }
|
||||
|
||||
Я подготовил для вас Docker-образ, в который включён Gunicorn управляющий процессами (воркерами) Uvicorn, в соответствии с концепциями рассмотренными в предыдущей главе: [Рабочие процессы сервера (воркеры) - Gunicorn совместно с Uvicorn](server-workers.md){.internal-link target=_blank}.
|
||||
Ранее существовал официальный Docker-образ FastAPI: <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker" class="external-link" target="_blank">tiangolo/uvicorn-gunicorn-fastapi</a>. Сейчас он помечен как устаревший. ⛔️
|
||||
|
||||
Этот образ может быть полезен для ситуаций описанных тут: [Множество процессов внутри контейнера для особых случаев](#_11).
|
||||
Скорее всего, вам **не стоит** использовать этот базовый образ (или какой-либо аналогичный).
|
||||
|
||||
* <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker" class="external-link" target="_blank">tiangolo/uvicorn-gunicorn-fastapi</a>.
|
||||
Если вы используете **Kubernetes** (или другое) и уже настраиваете **репликацию** на уровне кластера через несколько **контейнеров**, в этих случаях лучше **собрать образ с нуля**, как описано выше: [Создать Docker-образ для FastAPI](#build-a-docker-image-for-fastapi).
|
||||
|
||||
/// warning | Предупреждение
|
||||
А если вам нужны несколько воркеров, просто используйте опцию командной строки `--workers`.
|
||||
|
||||
Скорее всего у вас **нет необходимости** в использовании этого образа или подобного ему и лучше создать свой образ с нуля как описано тут: [Создать Docker-образ для FastAPI](#docker-fastapi).
|
||||
/// note | Технические подробности
|
||||
|
||||
Этот Docker-образ был создан в то время, когда Uvicorn не умел управлять и перезапускать «упавших» воркеров, и приходилось использовать Gunicorn вместе с Uvicorn, что добавляло заметную сложность, лишь бы Gunicorn управлял и перезапускал воркеров Uvicorn.
|
||||
|
||||
Но теперь, когда Uvicorn (и команда `fastapi`) поддерживают `--workers`, нет причин использовать базовый Docker-образ вместо сборки своего (кода получается примерно столько же 😅).
|
||||
|
||||
///
|
||||
|
||||
В этом образе есть **автоматический** механизм подстройки для запуска **необходимого количества процессов** в соответствии с доступным количеством ядер процессора.
|
||||
## Развёртывание образа контейнера { #deploy-the-container-image }
|
||||
|
||||
В нём установлены **разумные значения по умолчанию**, но можно изменять и обновлять конфигурацию с помощью **переменных окружения** или конфигурационных файлов.
|
||||
|
||||
Он также поддерживает прохождение <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker#pre_start_path" class="external-link" target="_blank">**Подготовительных шагов при запуске контейнеров**</a> при помощи скрипта.
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Для просмотра всех возможных настроек перейдите на страницу этого Docker-образа: <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker" class="external-link" target="_blank">tiangolo/uvicorn-gunicorn-fastapi</a>.
|
||||
|
||||
///
|
||||
|
||||
### Количество процессов в официальном Docker-образе
|
||||
|
||||
**Количество процессов** в этом образе **вычисляется автоматически** и зависит от доступного количества **ядер** центрального процессора.
|
||||
|
||||
Это означает, что он будет пытаться **выжать** из процессора как можно больше **производительности**.
|
||||
|
||||
Но вы можете изменять и обновлять конфигурацию с помощью **переменных окружения** и т.п.
|
||||
|
||||
Поскольку количество процессов зависит от процессора, на котором работает контейнер, **объём потребляемой памяти** также будет зависеть от этого.
|
||||
|
||||
А значит, если вашему приложению требуется много оперативной памяти (например, оно использует модели машинного обучения) и Ваш сервер имеет центральный процессор с большим количеством ядер, но **не слишком большим объёмом оперативной памяти**, то может дойти до того, что контейнер попытается занять памяти больше, чем доступно, из-за чего будет падение производительности (или сервер вовсе упадёт). 🚨
|
||||
|
||||
|
||||
### Написание `Dockerfile`
|
||||
|
||||
Итак, теперь мы можем написать `Dockerfile` основанный на этом официальном Docker-образе:
|
||||
|
||||
```Dockerfile
|
||||
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9
|
||||
|
||||
COPY ./requirements.txt /app/requirements.txt
|
||||
|
||||
RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt
|
||||
|
||||
COPY ./app /app
|
||||
```
|
||||
|
||||
### Большие приложения
|
||||
|
||||
Если вы успели ознакомиться с разделом [Приложения содержащие много файлов](../tutorial/bigger-applications.md){.internal-link target=_blank}, состоящие из множества файлов, Ваш Dockerfile может выглядеть так:
|
||||
|
||||
```Dockerfile hl_lines="7"
|
||||
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9
|
||||
|
||||
COPY ./requirements.txt /app/requirements.txt
|
||||
|
||||
RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt
|
||||
|
||||
COPY ./app /app/app
|
||||
```
|
||||
|
||||
### Как им пользоваться
|
||||
|
||||
Если вы используете **Kubernetes** (или что-то вроде того), скорее всего вам **не нужно** использовать официальный Docker-образ (или другой похожий) в качестве основы, так как управление **количеством запущенных контейнеров** должно быть настроено на уровне кластера. В таком случае лучше **создать образ с нуля**, как описано в разделе Создать [Docker-образ для FastAPI](#docker-fastapi).
|
||||
|
||||
Официальный образ может быть полезен в отдельных случаях, описанных выше в разделе [Множество процессов внутри контейнера для особых случаев](#_11). Например, если ваше приложение **достаточно простое**, не требует запуска в кластере и способно уместиться в один контейнер, то его настройки по умолчанию будут работать довольно хорошо. Или же вы развертываете его с помощью **Docker Compose**, работаете на одном сервере и т. д
|
||||
|
||||
## Развёртывание образа контейнера
|
||||
|
||||
После создания образа контейнера существует несколько способов его развёртывания.
|
||||
После того как у вас есть образ контейнера (Docker), его можно развёртывать несколькими способами.
|
||||
|
||||
Например:
|
||||
|
||||
* С использованием **Docker Compose** при развёртывании на одном сервере
|
||||
* С использованием **Kubernetes** в кластере
|
||||
* С использованием режима Docker Swarm в кластере
|
||||
* С использованием других инструментов, таких как Nomad
|
||||
* С использованием облачного сервиса, который будет управлять разворачиванием вашего контейнера
|
||||
* С **Docker Compose** на одном сервере
|
||||
* В кластере **Kubernetes**
|
||||
* В кластере Docker Swarm Mode
|
||||
* С другим инструментом, например Nomad
|
||||
* С облачным сервисом, который принимает ваш образ контейнера и разворачивает его
|
||||
|
||||
## Docker-образ и Poetry
|
||||
## Docker-образ с `uv` { #docker-image-with-uv }
|
||||
|
||||
Если вы пользуетесь <a href="https://python-poetry.org/" class="external-link" target="_blank">Poetry</a> для управления зависимостями вашего проекта, то можете использовать многоэтапную сборку образа:
|
||||
Если вы используете <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">uv</a> для установки и управления проектом, следуйте их <a href="https://docs.astral.sh/uv/guides/integration/docker/" class="external-link" target="_blank">руководству по Docker для uv</a>.
|
||||
|
||||
```{ .dockerfile .annotate }
|
||||
# (1)
|
||||
FROM python:3.9 as requirements-stage
|
||||
## Резюме { #recap }
|
||||
|
||||
# (2)
|
||||
WORKDIR /tmp
|
||||
Используя системы контейнеризации (например, **Docker** и **Kubernetes**), довольно просто закрыть все **концепции развертывания**:
|
||||
|
||||
# (3)
|
||||
RUN pip install poetry
|
||||
* HTTPS
|
||||
* Запуск при старте
|
||||
* Перезапуски
|
||||
* Репликация (количество запущенных процессов)
|
||||
* Память
|
||||
* Предварительные шаги перед запуском
|
||||
|
||||
# (4)
|
||||
COPY ./pyproject.toml ./poetry.lock* /tmp/
|
||||
В большинстве случаев вы, вероятно, не захотите использовать какой-либо базовый образ, а вместо этого **соберёте образ контейнера с нуля** на основе официального Docker-образа Python.
|
||||
|
||||
# (5)
|
||||
RUN poetry export -f requirements.txt --output requirements.txt --without-hashes
|
||||
|
||||
# (6)
|
||||
FROM python:3.9
|
||||
|
||||
# (7)
|
||||
WORKDIR /code
|
||||
|
||||
# (8)
|
||||
COPY --from=requirements-stage /tmp/requirements.txt /code/requirements.txt
|
||||
|
||||
# (9)
|
||||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
||||
|
||||
# (10)
|
||||
COPY ./app /code/app
|
||||
|
||||
# (11)
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
|
||||
```
|
||||
|
||||
1. Это первый этап, которому мы дадим имя `requirements-stage`.
|
||||
|
||||
2. Установите директорию `/tmp` в качестве рабочей директории.
|
||||
|
||||
В ней будет создан файл `requirements.txt`
|
||||
|
||||
3. На этом шаге установите Poetry.
|
||||
|
||||
4. Скопируйте файлы `pyproject.toml` и `poetry.lock` в директорию `/tmp`.
|
||||
|
||||
Поскольку название файла написано как `./poetry.lock*` (с `*` в конце), то ничего не сломается, если такой файл не будет найден.
|
||||
|
||||
5. Создайте файл `requirements.txt`.
|
||||
|
||||
6. Это второй (и последний) этап сборки, который и создаст окончательный образ контейнера.
|
||||
|
||||
7. Установите директорию `/code` в качестве рабочей.
|
||||
|
||||
8. Скопируйте файл `requirements.txt` в директорию `/code`.
|
||||
|
||||
Этот файл находится в образе, созданном на предыдущем этапе, которому мы дали имя requirements-stage, потому при копировании нужно написать `--from-requirements-stage`.
|
||||
|
||||
9. Установите зависимости, указанные в файле `requirements.txt`.
|
||||
|
||||
10. Скопируйте папку `app` в папку `/code`.
|
||||
|
||||
11. Запустите `uvicorn`, указав ему использовать объект `app`, расположенный в `app.main`.
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Если ткнёте на кружок с плюсом, то увидите объяснения, что происходит в этой строке.
|
||||
|
||||
///
|
||||
|
||||
**Этапы сборки Docker-образа** являются частью `Dockerfile` и работают как **временные образы контейнеров**. Они нужны только для создания файлов, используемых в дальнейших этапах.
|
||||
|
||||
Первый этап был нужен только для **установки Poetry** и **создания файла `requirements.txt`**, в которым прописаны зависимости вашего проекта, взятые из файла `pyproject.toml`.
|
||||
|
||||
На **следующем этапе** `pip` будет использовать файл `requirements.txt`.
|
||||
|
||||
В итоговом образе будет содержаться **только последний этап сборки**, предыдущие этапы будут отброшены.
|
||||
|
||||
При использовании Poetry, имеет смысл использовать **многоэтапную сборку Docker-образа**, потому что на самом деле вам не нужен Poetry и его зависимости в окончательном образе контейнера, вам **нужен только** сгенерированный файл `requirements.txt` для установки зависимостей вашего проекта.
|
||||
|
||||
А на последнем этапе, придерживаясь описанных ранее правил, создаётся итоговый образ
|
||||
|
||||
### Использование прокси-сервера завершения TLS и Poetry
|
||||
|
||||
И снова повторюсь, если используете прокси-сервер (балансировщик нагрузки), такой как Nginx или Traefik, добавьте в команду запуска опцию `--proxy-headers`:
|
||||
|
||||
```Dockerfile
|
||||
CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "80"]
|
||||
```
|
||||
|
||||
## Резюме
|
||||
|
||||
При помощи систем контейнеризации (таких, как **Docker** и **Kubernetes**), становится довольно просто обрабатывать все **концепции развертывания**:
|
||||
|
||||
* Использование более безопасного протокола HTTPS
|
||||
* Настройки запуска приложения
|
||||
* Перезагрузка приложения
|
||||
* Запуск нескольких экземпляров приложения
|
||||
* Управление памятью
|
||||
* Использование перечисленных функций перед запуском приложения
|
||||
|
||||
В большинстве случаев Вам, вероятно, не нужно использовать какой-либо базовый образ, **лучше создать образ контейнера с нуля** на основе официального Docker-образа Python.
|
||||
|
||||
Позаботившись о **порядке написания** инструкций в `Dockerfile`, вы сможете использовать **кэш Docker'а**, **минимизировав время сборки**, максимально повысив свою производительность (и не заскучать). 😎
|
||||
|
||||
В некоторых особых случаях вы можете использовать официальный образ Docker для FastAPI. 🤓
|
||||
Заботясь о **порядке** инструкций в `Dockerfile`и используя **кэш Docker**, вы можете **минимизировать время сборки**, чтобы повысить продуктивность (и не скучать). 😎
|
||||
|
||||
@@ -1,207 +1,231 @@
|
||||
# Об HTTPS
|
||||
# Об HTTPS { #about-https }
|
||||
|
||||
Обычно представляется, что HTTPS это некая опция, которая либо "включена", либо нет.
|
||||
Легко предположить, что HTTPS — это что-то, что просто «включено» или нет.
|
||||
|
||||
Но всё несколько сложнее.
|
||||
Но на самом деле всё гораздо сложнее.
|
||||
|
||||
/// tip | Заметка
|
||||
/// tip | Совет
|
||||
|
||||
Если вы торопитесь или вам не интересно, можете перейти на следующую страницу этого пошагового руководства по размещению приложений на серверах с использованием различных технологий.
|
||||
Если вы торопитесь или вам это не важно, переходите к следующим разделам с пошаговыми инструкциями по настройке всего разными способами.
|
||||
|
||||
///
|
||||
|
||||
Чтобы **изучить основы HTTPS** для клиента, перейдите по ссылке <a href="https://howhttps.works/" class="external-link" target="_blank">https://howhttps.works/</a>.
|
||||
Чтобы **изучить основы HTTPS** с точки зрения пользователя, загляните на <a href="https://howhttps.works/" class="external-link" target="_blank">https://howhttps.works/</a>.
|
||||
|
||||
Здесь же представлены некоторые концепции, которые **разработчик** должен иметь в виду при размышлениях об HTTPS:
|
||||
Теперь, со стороны **разработчика**, вот несколько вещей, которые стоит держать в голове, размышляя об HTTPS:
|
||||
|
||||
* Протокол HTTPS предполагает, что **серверу** нужно **располагать "сертификатами"** сгенерированными **третьей стороной**.
|
||||
* На самом деле эти сертификаты **приобретены** у третьей стороны, а не "сгенерированы".
|
||||
* У сертификатов есть **срок годности**.
|
||||
* Срок годности **истекает**.
|
||||
* По истечении срока годности их нужно **обновить**, то есть **снова получить** у третьей стороны.
|
||||
* Шифрование соединения происходит **на уровне протокола TCP**.
|
||||
* Протокол TCP находится на один уровень **ниже протокола HTTP**.
|
||||
* Поэтому **проверка сертификатов и шифрование** происходит **до HTTP**.
|
||||
* **TCP не знает о "доменах"**, но знает об IP-адресах.
|
||||
* Информация о **запрашиваемом домене** извлекается из запроса **на уровне HTTP**.
|
||||
* **Сертификаты HTTPS** "сертифицируют" **конкретный домен**, но проверка сертификатов и шифрование данных происходит на уровне протокола TCP, то есть **до того**, как станет известен домен-получатель данных.
|
||||
* **По умолчанию** это означает, что у вас может быть **только один сертификат HTTPS на один IP-адрес**.
|
||||
* Не важно, насколько большой у вас сервер и насколько маленькие приложения на нём могут быть.
|
||||
* Однако, у этой проблемы есть **решение**.
|
||||
* Существует **расширение** протокола **TLS** (который работает на уровне TCP, то есть до HTTP) называемое **<a href="https://en.wikipedia.org/wiki/Server_Name_Indication" class="external-link" target="_blank"><abbr title="Указание имени сервера">SNI</abbr></a>**.
|
||||
* Расширение SNI позволяет одному серверу (с **одним IP-адресом**) иметь **несколько сертификатов HTTPS** и обслуживать **множество HTTPS-доменов/приложений**.
|
||||
* Чтобы эта конструкция работала, **один** её компонент (программа) запущенный на сервере и слушающий **публичный IP-адрес**, должен иметь **все сертификаты HTTPS** для этого сервера.
|
||||
* **После** установления защищённого соединения, протоколом передачи данных **остаётся HTTP**.
|
||||
* Но данные теперь **зашифрованы**, несмотря на то, что они передаются по **протоколу HTTP**.
|
||||
* Для HTTPS **серверу** нужно **иметь «сертификаты»**, сгенерированные **третьей стороной**.
|
||||
* Эти сертификаты на самом деле **приобретаются** у третьей стороны, а не «генерируются».
|
||||
* У сертификатов есть **срок действия**.
|
||||
* Они **истекают**.
|
||||
* После этого их нужно **обновлять**, то есть **получать заново** у третьей стороны.
|
||||
* Шифрование соединения происходит на **уровне TCP**.
|
||||
* Это на один уровень **ниже HTTP**.
|
||||
* Поэтому **сертификаты и шифрование** обрабатываются **до HTTP**.
|
||||
* **TCP не знает о «доменах»**. Только об IP-адресах.
|
||||
* Информация о **конкретном домене** передаётся в **данных HTTP**.
|
||||
* **HTTPS-сертификаты** «сертифицируют» **определённый домен**, но протокол и шифрование происходят на уровне TCP, **до того как** становится известен домен, с которым идёт работа.
|
||||
* **По умолчанию** это означает, что вы можете иметь **лишь один HTTPS-сертификат на один IP-адрес**.
|
||||
* Неважно, насколько мощный у вас сервер или насколько маленькие приложения на нём работают.
|
||||
* Однако у этого есть **решение**.
|
||||
* Есть **расширение** протокола **TLS** (того самого, что занимается шифрованием на уровне TCP, до HTTP) под названием **<a href="https://en.wikipedia.org/wiki/Server_Name_Indication" class="external-link" target="_blank"><abbr title="Server Name Indication – Указание имени сервера">SNI</abbr></a>**.
|
||||
* Это расширение SNI позволяет одному серверу (с **одним IP-адресом**) иметь **несколько HTTPS-сертификатов** и обслуживать **несколько HTTPS-доменов/приложений**.
|
||||
* Чтобы это работало, **один** компонент (программа), запущенный на сервере и слушающий **публичный IP-адрес**, должен иметь **все HTTPS-сертификаты** на этом сервере.
|
||||
* **После** установления защищённого соединения, протокол обмена данными — **всё ещё HTTP**.
|
||||
* Содержимое **зашифровано**, несмотря на то, что оно отправляется по **протоколу HTTP**.
|
||||
|
||||
Обычной практикой является иметь **одну программу/HTTP-сервер** запущенную на сервере (машине, хосте и т.д.) и **ответственную за всю работу с HTTPS**:
|
||||
Обычно на сервере (машине, хосте и т.п.) запускают **одну программу/HTTP‑сервер**, которая **управляет всей частью, связанной с HTTPS**: принимает **зашифрованные HTTPS-запросы**, отправляет **расшифрованные HTTP-запросы** в само HTTP‑приложение, работающее на том же сервере (в нашем случае это приложение **FastAPI**), получает **HTTP-ответ** от приложения, **шифрует его** с использованием подходящего **HTTPS‑сертификата** и отправляет клиенту по **HTTPS**. Такой сервер часто называют **<a href="https://en.wikipedia.org/wiki/TLS_termination_proxy" class="external-link" target="_blank">прокси‑сервером TLS-терминации</a>**.
|
||||
|
||||
* получение **зашифрованных HTTPS-запросов**
|
||||
* отправка **расшифрованных HTTP запросов** в соответствующее HTTP-приложение, работающее на том же сервере (в нашем случае, это приложение **FastAPI**)
|
||||
* получение **HTTP-ответа** от приложения
|
||||
* **шифрование ответа** используя подходящий **сертификат HTTPS**
|
||||
* отправка зашифрованного **HTTPS-ответа клиенту**.
|
||||
Такой сервер часто называют **<a href="https://en.wikipedia.org/wiki/TLS_termination_proxy" class="external-link" target="_blank">Прокси-сервер завершения работы TLS</a>** или просто "прокси-сервер".
|
||||
Некоторые варианты, которые вы можете использовать как прокси‑сервер TLS-терминации:
|
||||
|
||||
Вот некоторые варианты, которые вы можете использовать в качестве такого прокси-сервера:
|
||||
|
||||
* Traefik (может обновлять сертификаты)
|
||||
* Caddy (может обновлять сертификаты)
|
||||
* Traefik (умеет обновлять сертификаты)
|
||||
* Caddy (умеет обновлять сертификаты)
|
||||
* Nginx
|
||||
* HAProxy
|
||||
|
||||
## Let's Encrypt (центр сертификации)
|
||||
## Let's Encrypt { #lets-encrypt }
|
||||
|
||||
До появления Let's Encrypt **сертификаты HTTPS** приходилось покупать у третьих сторон.
|
||||
До появления Let's Encrypt эти **HTTPS-сертификаты** продавались доверенными третьими сторонами.
|
||||
|
||||
Процесс получения такого сертификата был трудоёмким, требовал предоставления подтверждающих документов и сертификаты стоили дорого.
|
||||
Процесс получения таких сертификатов был неудобным, требовал бумажной волокиты, а сами сертификаты были довольно дорогими.
|
||||
|
||||
Но затем консорциумом Linux Foundation был создан проект **<a href="https://letsencrypt.org/" class="external-link" target="_blank">Let's Encrypt</a>**.
|
||||
Затем появился **<a href="https://letsencrypt.org/" class="external-link" target="_blank">Let's Encrypt</a>**.
|
||||
|
||||
Он автоматически предоставляет **бесплатные сертификаты HTTPS**. Эти сертификаты используют все стандартные криптографические способы шифрования. Они имеют небольшой срок годности (около 3 месяцев), благодаря чему они даже **более безопасны**.
|
||||
Это проект Linux Foundation. Он предоставляет **HTTPS‑сертификаты бесплатно**, в автоматическом режиме. Эти сертификаты используют стандартные криптографические механизмы и имеют короткий срок действия (около 3 месяцев), поэтому **безопасность фактически выше** благодаря уменьшенному сроку жизни.
|
||||
|
||||
При запросе на получение сертификата, он автоматически генерируется и домен проверяется на безопасность. Это позволяет обновлять сертификаты автоматически.
|
||||
Домены безопасно проверяются, а сертификаты выдаются автоматически. Это также позволяет автоматизировать процесс их продления.
|
||||
|
||||
Суть идеи в автоматическом получении и обновлении этих сертификатов, чтобы все могли пользоваться **безопасным HTTPS. Бесплатно. В любое время.**
|
||||
Идея — автоматизировать получение и продление сертификатов, чтобы у вас был **безопасный HTTPS, бесплатно и навсегда**.
|
||||
|
||||
## HTTPS для разработчиков
|
||||
## HTTPS для разработчиков { #https-for-developers }
|
||||
|
||||
Ниже, шаг за шагом, с заострением внимания на идеях, важных для разработчика, описано, как может выглядеть HTTPS API.
|
||||
Ниже приведён пример того, как может выглядеть HTTPS‑API, шаг за шагом, с акцентом на идеях, важных для разработчиков.
|
||||
|
||||
### Имя домена
|
||||
### Имя домена { #domain-name }
|
||||
|
||||
Чаще всего, всё начинается с **приобретения имени домена**. Затем нужно настроить DNS-сервер (вероятно у того же провайдера, который выдал вам домен).
|
||||
Чаще всего всё начинается с **приобретения** **имени домена**. Затем вы настраиваете его на DNS‑сервере (возможно, у того же облачного провайдера).
|
||||
|
||||
Далее, возможно, вы получаете "облачный" сервер (виртуальную машину) или что-то типа этого, у которого есть <abbr title="Не изменяемый">постоянный</abbr> **публичный IP-адрес**.
|
||||
Скорее всего, вы получите облачный сервер (виртуальную машину) или что-то подобное, и у него будет <abbr title="Не изменяется">постоянный</abbr> **публичный IP-адрес**.
|
||||
|
||||
На DNS-сервере (серверах) вам следует настроить соответствующую ресурсную запись ("`запись A`"), указав, что **Ваш домен** связан с публичным **IP-адресом вашего сервера**.
|
||||
На DNS‑сервере(ах) вы настроите запись («`A record`» - запись типа A), указывающую, что **ваш домен** должен указывать на публичный **IP‑адрес вашего сервера**.
|
||||
|
||||
Обычно эту запись достаточно указать один раз, при первоначальной настройке всего сервера.
|
||||
Обычно это делается один раз — при первоначальной настройке всего.
|
||||
|
||||
/// tip | Заметка
|
||||
/// tip | Совет
|
||||
|
||||
Уровни протоколов, работающих с именами доменов, намного ниже HTTPS, но об этом следует упомянуть здесь, так как всё зависит от доменов и IP-адресов.
|
||||
Часть про доменное имя относится к этапам задолго до HTTPS, но так как всё зависит от домена и IP‑адреса, здесь стоит это упомянуть.
|
||||
|
||||
///
|
||||
|
||||
### DNS
|
||||
### DNS { #dns }
|
||||
|
||||
Теперь давайте сфокусируемся на работе с HTTPS.
|
||||
Теперь сфокусируемся на собственно частях, связанных с HTTPS.
|
||||
|
||||
Всё начинается с того, что браузер спрашивает у **DNS-серверов**, какой **IP-адрес связан с доменом**, для примера возьмём домен `someapp.example.com`.
|
||||
Сначала браузер спросит у **DNS‑серверов**, какой **IP соответствует домену**, в нашем примере `someapp.example.com`.
|
||||
|
||||
DNS-сервера присылают браузеру определённый **IP-адрес**, тот самый публичный IP-адрес вашего сервера, который вы указали в ресурсной "записи А" при настройке.
|
||||
DNS‑серверы ответят браузеру, какой **конкретный IP‑адрес** использовать. Это будет публичный IP‑адрес вашего сервера, который вы указали в настройках DNS.
|
||||
|
||||
<img src="/img/deployment/https/https01.drawio.svg">
|
||||
|
||||
### Рукопожатие TLS
|
||||
### Начало TLS-рукопожатия { #tls-handshake-start }
|
||||
|
||||
В дальнейшем браузер будет взаимодействовать с этим IP-адресом через **port 443** (общепринятый номер порта для HTTPS).
|
||||
Далее браузер будет общаться с этим IP‑адресом на **порту 443** (порт HTTPS).
|
||||
|
||||
Первым шагом будет установление соединения между клиентом (браузером) и сервером и выбор криптографического ключа (для шифрования).
|
||||
Первая часть взаимодействия — установить соединение между клиентом и сервером и договориться о криптографических ключах и т.п.
|
||||
|
||||
<img src="/img/deployment/https/https02.drawio.svg">
|
||||
|
||||
Эта часть клиент-серверного взаимодействия устанавливает TLS-соединение и называется **TLS-рукопожатием**.
|
||||
Это взаимодействие клиента и сервера для установления TLS‑соединения называется **TLS‑рукопожатием**.
|
||||
|
||||
### TLS с расширением SNI
|
||||
### TLS с расширением SNI { #tls-with-sni-extension }
|
||||
|
||||
На сервере **только один процесс** может прослушивать определённый **порт** определённого **IP-адреса**. На сервере могут быть и другие процессы, слушающие другие порты этого же IP-адреса, но никакой процесс не может быть привязан к уже занятой комбинации IP-адрес:порт. Эта комбинация называется "сокет".
|
||||
На сервере **только один процесс** может слушать конкретный **порт** на конкретном **IP‑адресе**. Могут быть другие процессы, слушающие другие порты на том же IP‑адресе, но не более одного процесса на каждую комбинацию IP‑адреса и порта.
|
||||
|
||||
По умолчанию TLS (HTTPS) использует порт `443`. Потому этот же порт будем использовать и мы.
|
||||
По умолчанию TLS (HTTPS) использует порт `443`. Значит, он нам и нужен.
|
||||
|
||||
И раз уж только один процесс может слушать этот порт, то это будет процесс **прокси-сервера завершения работы TLS**.
|
||||
Так как только один процесс может слушать этот порт, делать это будет **прокси‑сервер TLS-терминации**.
|
||||
|
||||
Прокси-сервер завершения работы TLS будет иметь доступ к одному или нескольким **TLS-сертификатам** (сертификаты HTTPS).
|
||||
У прокси‑сервера TLS-терминации будет доступ к одному или нескольким **TLS‑сертификатам** (HTTPS‑сертификатам).
|
||||
|
||||
Используя **расширение SNI** упомянутое выше, прокси-сервер из имеющихся сертификатов TLS (HTTPS) выберет тот, который соответствует имени домена, указанному в запросе от клиента.
|
||||
Используя **расширение SNI**, упомянутое выше, прокси‑сервер TLS-терминации определит, какой из доступных TLS (HTTPS)‑сертификатов нужно использовать для этого соединения, выбрав тот, который соответствует домену, ожидаемому клиентом.
|
||||
|
||||
То есть будет выбран сертификат для домена `someapp.example.com`.
|
||||
В нашем случае это будет сертификат для `someapp.example.com`.
|
||||
|
||||
<img src="/img/deployment/https/https03.drawio.svg">
|
||||
|
||||
Клиент уже **доверяет** тому, кто выдал этот TLS-сертификат (в нашем случае - Let's Encrypt, но мы ещё обсудим это), потому может **проверить**, действителен ли полученный от сервера сертификат.
|
||||
Клиент уже **доверяет** организации, выдавшей этот TLS‑сертификат (в нашем случае — Let's Encrypt, но об этом позже), поэтому может **проверить**, что сертификат действителен.
|
||||
|
||||
Затем, используя этот сертификат, клиент и прокси-сервер **выбирают способ шифрования** данных для устанавливаемого **TCP-соединения**. На этом операция **TLS-рукопожатия** завершена.
|
||||
Затем, используя сертификат, клиент и прокси‑сервер TLS-терминации **договариваются о способе шифрования** остальной **TCP‑коммуникации**. На этом **TLS‑рукопожатие** завершено.
|
||||
|
||||
В дальнейшем клиент и сервер будут взаимодействовать по **зашифрованному TCP-соединению**, как предлагается в протоколе TLS. И на основе этого TCP-соедениния будет создано **HTTP-соединение**.
|
||||
После этого у клиента и сервера есть **зашифрованное TCP‑соединение** — это и предоставляет TLS. И они могут использовать это соединение, чтобы начать собственно **HTTP‑обмен**.
|
||||
|
||||
Таким образом, **HTTPS** это тот же **HTTP**, но внутри **безопасного TLS-соединения** вместо чистого (незашифрованного) TCP-соединения.
|
||||
Собственно, **HTTPS** — это обычный **HTTP** внутри **защищённого TLS‑соединения**, вместо чистого (незашифрованного) TCP‑соединения.
|
||||
|
||||
/// tip | Заметка
|
||||
/// tip | Совет
|
||||
|
||||
Обратите внимание, что шифрование происходит на **уровне TCP**, а не на более высоком уровне HTTP.
|
||||
Обратите внимание, что шифрование обмена происходит на **уровне TCP**, а не на уровне HTTP.
|
||||
|
||||
///
|
||||
|
||||
### HTTPS-запрос
|
||||
### HTTPS‑запрос { #https-request }
|
||||
|
||||
Теперь, когда между клиентом и сервером (в нашем случае, браузером и прокси-сервером) создано **зашифрованное TCP-соединение**, они могут начать **обмен данными по протоколу HTTP**.
|
||||
Теперь, когда у клиента и сервера (конкретно у браузера и прокси‑сервера TLS-терминации) есть **зашифрованное TCP‑соединение**, они могут начать **HTTP‑обмен**.
|
||||
|
||||
Так клиент отправляет **HTTPS-запрос**. То есть обычный HTTP-запрос, но через зашифрованное TLS-содинение.
|
||||
Клиент отправляет **HTTPS‑запрос**. Это обычный HTTP‑запрос через зашифрованное TLS‑соединение.
|
||||
|
||||
<img src="/img/deployment/https/https04.drawio.svg">
|
||||
|
||||
### Расшифровка запроса
|
||||
### Расшифровка запроса { #decrypt-the-request }
|
||||
|
||||
Прокси-сервер, используя согласованный с клиентом ключ, расшифрует полученный **зашифрованный запрос** и передаст **обычный (незашифрованный) HTTP-запрос** процессу, запускающему приложение (например, процессу Uvicorn запускающему приложение FastAPI).
|
||||
Прокси‑сервер TLS-терминации использует согласованное шифрование, чтобы **расшифровать запрос**, и передаёт **обычный (расшифрованный) HTTP‑запрос** процессу, запускающему приложение (например, процессу с Uvicorn, который запускает приложение FastAPI).
|
||||
|
||||
<img src="/img/deployment/https/https05.drawio.svg">
|
||||
|
||||
### HTTP-ответ
|
||||
### HTTP‑ответ { #http-response }
|
||||
|
||||
Приложение обработает запрос и вернёт **обычный (незашифрованный) HTTP-ответ** прокси-серверу.
|
||||
Приложение обработает запрос и отправит **обычный (незашифрованный) HTTP‑ответ** прокси‑серверу TLS-терминации.
|
||||
|
||||
<img src="/img/deployment/https/https06.drawio.svg">
|
||||
|
||||
### HTTPS-ответ
|
||||
### HTTPS‑ответ { #https-response }
|
||||
|
||||
Пркоси-сервер **зашифрует ответ** используя ранее согласованный с клиентом способ шифрования (которые содержатся в сертификате для домена `someapp.example.com`) и отправит его браузеру.
|
||||
Затем прокси‑сервер TLS-терминации **зашифрует ответ** с использованием ранее согласованного способа шифрования (который начали использовать для сертификата для `someapp.example.com`) и отправит его обратно в браузер.
|
||||
|
||||
Наконец, браузер проверит ответ, в том числе, что тот зашифрован с нужным ключом, **расшифрует его** и обработает.
|
||||
Далее браузер проверит, что ответ корректен и зашифрован правильным криптографическим ключом и т.п., затем **расшифрует ответ** и обработает его.
|
||||
|
||||
<img src="/img/deployment/https/https07.drawio.svg">
|
||||
|
||||
Клиент (браузер) знает, что ответ пришёл от правильного сервера, так как использует методы шифрования, согласованные ими раннее через **HTTPS-сертификат**.
|
||||
Клиент (браузер) узнает, что ответ пришёл от правильного сервера, потому что используется способ шифрования, о котором они договорились ранее с помощью **HTTPS‑сертификата**.
|
||||
|
||||
### Множество приложений
|
||||
### Несколько приложений { #multiple-applications }
|
||||
|
||||
На одном и том же сервере (или серверах) можно разместить **множество приложений**, например, другие программы с API или базы данных.
|
||||
На одном и том же сервере (или серверах) могут работать **несколько приложений**, например другие программы с API или база данных.
|
||||
|
||||
Напомню, что только один процесс (например, прокси-сервер) может прослушивать определённый порт определённого IP-адреса.
|
||||
Но другие процессы и приложения тоже могут работать на этом же сервере (серверах), если они не пытаются использовать уже занятую **комбинацию IP-адреса и порта** (сокет).
|
||||
Только один процесс может обрабатывать конкретную комбинацию IP и порта (в нашем примере — прокси‑сервер TLS-терминации), но остальные приложения/процессы тоже могут работать на сервере(ах), пока они не пытаются использовать ту же **комбинацию публичного IP и порта**.
|
||||
|
||||
<img src="/img/deployment/https/https08.drawio.svg">
|
||||
|
||||
Таким образом, сервер завершения TLS может обрабатывать HTTPS-запросы и использовать сертификаты для **множества доменов** или приложений и передавать запросы правильным адресатам (другим приложениям).
|
||||
Таким образом, прокси‑сервер TLS-терминации может обрабатывать HTTPS и сертификаты для **нескольких доменов** (для нескольких приложений), а затем передавать запросы нужному приложению в каждом случае.
|
||||
|
||||
### Обновление сертификата
|
||||
### Продление сертификата { #certificate-renewal }
|
||||
|
||||
В недалёком будущем любой сертификат станет **просроченным** (примерно через три месяца после получения).
|
||||
Со временем каждый сертификат **истечёт** (примерно через 3 месяца после получения).
|
||||
|
||||
Когда это произойдёт, можно запустить другую программу, которая подключится к Let's Encrypt и обновит сертификат(ы). Существуют прокси-серверы, которые могут сделать это действие самостоятельно.
|
||||
Затем будет другая программа (иногда это отдельная программа, иногда — тот же прокси‑сервер TLS-терминации), которая свяжется с Let's Encrypt и продлит сертификат(ы).
|
||||
|
||||
<img src="/img/deployment/https/https.drawio.svg">
|
||||
|
||||
**TLS-сертификаты** не привязаны к IP-адресу, но **связаны с именем домена**.
|
||||
**TLS‑сертификаты** **связаны с именем домена**, а не с IP‑адресом.
|
||||
|
||||
Так что при обновлении сертификатов программа должна **подтвердить** центру сертификации (Let's Encrypt), что обновление запросил **"владелец", который контролирует этот домен**.
|
||||
Поэтому, чтобы продлить сертификаты, программа продления должна **доказать** удостоверяющему центру (Let's Encrypt), что она действительно **«владеет» и контролирует этот домен**.
|
||||
|
||||
Есть несколько путей осуществления этого. Самые популярные из них:
|
||||
Для этого, учитывая разные потребности приложений, есть несколько способов. Популярные из них:
|
||||
|
||||
* **Изменение записей DNS**.
|
||||
* Для такого варианта Ваша программа обновления должна уметь работать с API DNS-провайдера, обслуживающего Ваши DNS-записи. Не у всех провайдеров есть такой API, так что этот способ не всегда применим.
|
||||
* **Запуск в качестве программы-сервера** (как минимум, на время обновления сертификатов) на публичном IP-адресе домена.
|
||||
* Как уже не раз упоминалось, только один процесс может прослушивать определённый порт определённого IP-адреса.
|
||||
* Это одна из причин использования прокси-сервера ещё и в качестве программы обновления сертификатов.
|
||||
* В случае, если обновлением сертификатов занимается другая программа, вам понадобится остановить прокси-сервер, запустить программу обновления сертификатов на сокете, предназначенном для прокси-сервера, настроить прокси-сервер на работу с новыми сертификатами и перезапустить его. Эта схема далека от идеальной, так как Ваши приложения будут недоступны на время отключения прокси-сервера.
|
||||
* **Изменить некоторые DNS‑записи**.
|
||||
* Для этого программа продления должна поддерживать API DNS‑провайдера, поэтому, в зависимости от используемого провайдера DNS, этот вариант может быть доступен или нет.
|
||||
* **Запуститься как сервер** (как минимум на время получения сертификатов) на публичном IP‑адресе, связанном с доменом.
|
||||
* Как сказано выше, только один процесс может слушать конкретный IP и порт.
|
||||
* Это одна из причин, почему очень удобно, когда тот же прокси‑сервер TLS-терминации также занимается процессом продления сертификатов.
|
||||
* В противном случае вам, возможно, придётся временно остановить прокси‑сервер TLS-терминации, запустить программу продления для получения сертификатов, затем настроить их в прокси‑сервере TLS-терминации и перезапустить его. Это не идеально, так как ваше приложение(я) будут недоступны, пока прокси‑сервер TLS-терминации остановлен.
|
||||
|
||||
Весь этот процесс обновления, одновременный с обслуживанием запросов, является одной из основных причин, по которой желательно иметь **отдельную систему для работы с HTTPS** в виде прокси-сервера завершения TLS, а не просто использовать сертификаты TLS непосредственно с сервером приложений (например, Uvicorn).
|
||||
Весь этот процесс продления, совмещённый с обслуживанием приложения, — одна из главных причин иметь **отдельную систему для работы с HTTPS** в виде прокси‑сервера TLS-терминации, вместо использования TLS‑сертификатов напрямую в сервере приложения (например, Uvicorn).
|
||||
|
||||
## Резюме
|
||||
## Пересылаемые HTTP-заголовки прокси { #proxy-forwarded-headers }
|
||||
|
||||
Наличие **HTTPS** очень важно и довольно **критично** в большинстве случаев. Однако, Вам, как разработчику, не нужно тратить много сил на это, достаточно **понимать эти концепции** и принципы их работы.
|
||||
Когда вы используете прокси для обработки HTTPS, ваш **сервер приложения** (например, Uvicorn через FastAPI CLI) ничего не знает о процессе HTTPS, он общается обычным HTTP с **прокси‑сервером TLS-терминации**.
|
||||
|
||||
Но узнав базовые основы **HTTPS** вы можете легко совмещать разные инструменты, которые помогут вам в дальнейшей разработке.
|
||||
Обычно этот **прокси** на лету добавляет некоторые HTTP‑заголовки перед тем, как переслать запрос на **сервер приложения**, чтобы тот знал, что запрос был **проксирован**.
|
||||
|
||||
В следующих главах я покажу вам несколько примеров, как настраивать **HTTPS** для приложений **FastAPI**. 🔒
|
||||
/// note | Технические детали
|
||||
|
||||
Заголовки прокси:
|
||||
|
||||
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-For" class="external-link" target="_blank">X-Forwarded-For</a>
|
||||
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Proto" class="external-link" target="_blank">X-Forwarded-Proto</a>
|
||||
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Host" class="external-link" target="_blank">X-Forwarded-Host</a>
|
||||
|
||||
///
|
||||
|
||||
Тем не менее, так как **сервер приложения** не знает, что он находится за доверенным **прокси**, по умолчанию он не будет доверять этим заголовкам.
|
||||
|
||||
Но вы можете настроить **сервер приложения**, чтобы он доверял *пересылаемым* заголовкам, отправленным **прокси**. Если вы используете FastAPI CLI, вы можете использовать *опцию CLI* `--forwarded-allow-ips`, чтобы указать, с каких IP‑адресов следует доверять этим *пересылаемым* заголовкам.
|
||||
|
||||
Например, если **сервер приложения** получает запросы только от доверенного **прокси**, вы можете установить `--forwarded-allow-ips="*"`, чтобы доверять всем входящим IP, так как он всё равно будет получать запросы только с IP‑адреса, используемого **прокси**.
|
||||
|
||||
Таким образом, приложение сможет знать свой публичный URL, использует ли оно HTTPS, какой домен и т.п.
|
||||
|
||||
Это будет полезно, например, для корректной обработки редиректов.
|
||||
|
||||
/// tip | Совет
|
||||
|
||||
Подробнее об этом вы можете узнать в документации: [За прокси — Включить пересылаемые заголовки прокси](../advanced/behind-a-proxy.md#enable-proxy-forwarded-headers){.internal-link target=_blank}
|
||||
|
||||
///
|
||||
|
||||
## Резюме { #recap }
|
||||
|
||||
Наличие **HTTPS** очень важно и во многих случаях довольно **критично**. Большая часть усилий, которые вам, как разработчику, нужно приложить вокруг HTTPS, — это просто **понимание этих концепций** и того, как они работают.
|
||||
|
||||
Зная базовую информацию о **HTTPS для разработчиков**, вы сможете легко комбинировать и настраивать разные инструменты, чтобы управлять всем этим простым способом.
|
||||
|
||||
В некоторых из следующих глав я покажу вам несколько конкретных примеров настройки **HTTPS** для приложений **FastAPI**. 🔒
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
# Развёртывание
|
||||
# Развёртывание { #deployment }
|
||||
|
||||
Развернуть приложение **FastAPI** довольно просто.
|
||||
|
||||
## Да что такое это ваше - "развёртывание"?!
|
||||
## Что означает развёртывание { #what-does-deployment-mean }
|
||||
|
||||
Термин **развёртывание** (приложения) означает выполнение необходимых шагов, чтобы сделать приложение **доступным для пользователей**.
|
||||
|
||||
Обычно **веб-приложения** размещают на удалённом компьютере с серверной программой, которая обеспечивает хорошую производительность, стабильность и т. д., Чтобы ваши пользователи могли эффективно, беспрерывно и беспроблемно обращаться к приложению.
|
||||
Для **веб-API** это обычно означает размещение его на **удалённой машине** с **серверной программой**, обеспечивающей хорошую производительность, стабильность и т.д., чтобы ваши **пользователи** могли **получать доступ** к приложению эффективно и без перебоев или проблем.
|
||||
|
||||
Это отличается от **разработки**, когда вы постоянно меняете код, делаете в нём намеренные ошибки и исправляете их, останавливаете и перезапускаете сервер разработки и т. д.
|
||||
Это отличается от этапов **разработки**, когда вы постоянно меняете код, ломаете его и исправляете, останавливаете и перезапускаете сервер разработки и т.д.
|
||||
|
||||
## Стратегии развёртывания
|
||||
## Стратегии развёртывания { #deployment-strategies }
|
||||
|
||||
В зависимости от вашего конкретного случая, есть несколько способов сделать это.
|
||||
|
||||
|
||||
@@ -1,33 +1,82 @@
|
||||
# Запуск сервера вручную - Uvicorn
|
||||
# Запуск сервера вручную { #run-a-server-manually }
|
||||
|
||||
Для запуска приложения **FastAPI** на удалённой серверной машине вам необходим программный сервер, поддерживающий протокол ASGI, такой как **Uvicorn**.
|
||||
## Используйте команду `fastapi run` { #use-the-fastapi-run-command }
|
||||
|
||||
Существует три наиболее распространённые альтернативы:
|
||||
Коротко: используйте `fastapi run`, чтобы запустить ваше приложение FastAPI:
|
||||
|
||||
* <a href="https://www.uvicorn.org/" class="external-link" target="_blank">Uvicorn</a>: высокопроизводительный ASGI сервер.
|
||||
* <a href="https://hypercorn.readthedocs.io/" class="external-link" target="_blank">Hypercorn</a>: ASGI сервер, помимо прочего поддерживающий HTTP/2 и Trio.
|
||||
* <a href="https://github.com/django/daphne" class="external-link" target="_blank">Daphne</a>: ASGI сервер, созданный для Django Channels.
|
||||
<div class="termy">
|
||||
|
||||
## Сервер как машина и сервер как программа
|
||||
```console
|
||||
$ <font color="#4E9A06">fastapi</font> run <u style="text-decoration-style:solid">main.py</u>
|
||||
|
||||
В этих терминах есть некоторые различия и вам следует запомнить их. 💡
|
||||
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting production server 🚀
|
||||
|
||||
Слово "**сервер**" чаще всего используется в двух контекстах:
|
||||
Searching for package file structure from directories
|
||||
with <font color="#3465A4">__init__.py</font> files
|
||||
Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
|
||||
|
||||
- удалённый или расположенный в "облаке" компьютер (физическая или виртуальная машина).
|
||||
- программа, запущенная на таком компьютере (например, Uvicorn).
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> 🐍 main.py
|
||||
|
||||
Просто запомните, если вам встретился термин "сервер", то обычно он подразумевает что-то из этих двух смыслов.
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with
|
||||
the following code:
|
||||
|
||||
Когда имеют в виду именно удалённый компьютер, часто говорят просто **сервер**, но ещё его называют **машина**, **ВМ** (виртуальная машина), **нода**. Все эти термины обозначают одно и то же - удалённый компьютер, обычно под управлением Linux, на котором вы запускаете программы.
|
||||
<u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u>
|
||||
|
||||
## Установка программного сервера
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font>
|
||||
|
||||
Вы можете установить сервер, совместимый с протоколом ASGI, так:
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000</u></font>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000/docs</u></font>
|
||||
|
||||
//// tab | Uvicorn
|
||||
Logs:
|
||||
|
||||
* <a href="https://www.uvicorn.org/" class="external-link" target="_blank">Uvicorn</a>, очень быстрый ASGI сервер, основанный на библиотеках uvloop и httptools.
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>2306215</b></font><b>]</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000</u></font> <b>(</b>Press CTRL+C
|
||||
to quit<b>)</b>
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
В большинстве случаев этого достаточно. 😎
|
||||
|
||||
Этой командой, например, можно запускать приложение **FastAPI** в контейнере, на сервере и т.д.
|
||||
|
||||
## ASGI‑серверы { #asgi-servers }
|
||||
|
||||
Давайте немного углубимся в детали.
|
||||
|
||||
FastAPI использует стандарт для построения Python‑веб‑фреймворков и серверов под названием <abbr title="Asynchronous Server Gateway Interface – Асинхронный шлюзовый интерфейс сервера">ASGI</abbr>. FastAPI — ASGI-веб‑фреймворк.
|
||||
|
||||
Главное, что вам нужно, чтобы запустить приложение **FastAPI** (или любое другое ASGI‑приложение) на удалённой серверной машине, — это программа ASGI‑сервера, такая как **Uvicorn**; именно он используется по умолчанию в команде `fastapi`.
|
||||
|
||||
Есть несколько альтернатив, например:
|
||||
|
||||
* <a href="https://www.uvicorn.org/" class="external-link" target="_blank">Uvicorn</a>: высокопроизводительный ASGI‑сервер.
|
||||
* <a href="https://hypercorn.readthedocs.io/" class="external-link" target="_blank">Hypercorn</a>: ASGI‑сервер, среди прочего совместимый с HTTP/2 и Trio.
|
||||
* <a href="https://github.com/django/daphne" class="external-link" target="_blank">Daphne</a>: ASGI‑сервер, созданный для Django Channels.
|
||||
* <a href="https://github.com/emmett-framework/granian" class="external-link" target="_blank">Granian</a>: HTTP‑сервер на Rust для Python‑приложений.
|
||||
* <a href="https://unit.nginx.org/howto/fastapi/" class="external-link" target="_blank">NGINX Unit</a>: NGINX Unit — лёгкая и многофункциональная среда выполнения веб‑приложений.
|
||||
|
||||
## Сервер как машина и сервер как программа { #server-machine-and-server-program }
|
||||
|
||||
Есть небольшой нюанс в терминологии, о котором стоит помнить. 💡
|
||||
|
||||
Слово «сервер» обычно используют и для обозначения удалённого/облачного компьютера (физической или виртуальной машины), и для программы, работающей на этой машине (например, Uvicorn).
|
||||
|
||||
Имейте в виду, что слово «сервер» в целом может означать любое из этих двух.
|
||||
|
||||
Когда речь идёт об удалённой машине, её зачастую называют **сервер**, а также **машина**, **VM** (виртуальная машина), **нода**. Всё это — варианты названия удалённой машины, обычно под управлением Linux, на которой вы запускаете программы.
|
||||
|
||||
## Установка серверной программы { #install-the-server-program }
|
||||
|
||||
При установке FastAPI он поставляется с продакшн‑сервером Uvicorn, и вы можете запустить его командой `fastapi run`.
|
||||
|
||||
Но вы также можете установить ASGI‑сервер вручную.
|
||||
|
||||
Создайте [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активируйте его и затем установите серверное приложение.
|
||||
|
||||
Например, чтобы установить Uvicorn:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -39,39 +88,21 @@ $ pip install "uvicorn[standard]"
|
||||
|
||||
</div>
|
||||
|
||||
/// tip | Подсказка
|
||||
Аналогично устанавливаются и другие ASGI‑серверы.
|
||||
|
||||
С опцией `standard`, Uvicorn будет устанавливаться и использоваться с некоторыми дополнительными рекомендованными зависимостями.
|
||||
/// tip | Совет
|
||||
|
||||
В них входит `uvloop`, высокопроизводительная замена `asyncio`, которая значительно ускоряет работу асинхронных программ.
|
||||
С добавлением `standard` Uvicorn установит и будет использовать ряд рекомендованных дополнительных зависимостей.
|
||||
|
||||
В их числе `uvloop` — высокопроизводительная замена `asyncio`, дающая серьёзный прирост производительности при параллельной работе.
|
||||
|
||||
Если вы устанавливаете FastAPI, например так: `pip install "fastapi[standard]"`, вы уже получаете и `uvicorn[standard]`.
|
||||
|
||||
///
|
||||
|
||||
////
|
||||
## Запуск серверной программы { #run-the-server-program }
|
||||
|
||||
//// tab | Hypercorn
|
||||
|
||||
* <a href="https://github.com/pgjones/hypercorn" class="external-link" target="_blank">Hypercorn</a>, ASGI сервер, поддерживающий протокол HTTP/2.
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install hypercorn
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
...или какой-либо другой ASGI сервер.
|
||||
|
||||
////
|
||||
|
||||
## Запуск серверной программы
|
||||
|
||||
Затем запустите ваше приложение так же, как было указано в руководстве ранее, но без опции `--reload`:
|
||||
|
||||
//// tab | Uvicorn
|
||||
Если вы установили ASGI‑сервер вручную, обычно нужно передать строку импорта в специальном формате, чтобы он смог импортировать ваше приложение FastAPI:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -83,81 +114,44 @@ $ uvicorn main:app --host 0.0.0.0 --port 80
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
/// note | Примечание
|
||||
|
||||
//// tab | Hypercorn
|
||||
Команда `uvicorn main:app` означает:
|
||||
|
||||
<div class="termy">
|
||||
* `main`: файл `main.py` (Python‑«модуль»).
|
||||
* `app`: объект, созданный в `main.py` строкой `app = FastAPI()`.
|
||||
|
||||
```console
|
||||
$ hypercorn main:app --bind 0.0.0.0:80
|
||||
Эквивалентно:
|
||||
|
||||
Running on 0.0.0.0:8080 over http (CTRL + C to quit)
|
||||
```Python
|
||||
from main import app
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
/// warning | Предупреждение
|
||||
|
||||
Не забудьте удалить опцию `--reload`, если ранее пользовались ею.
|
||||
|
||||
Включение опции `--reload` требует дополнительных ресурсов, влияет на стабильность работы приложения и может повлечь прочие неприятности.
|
||||
|
||||
Она сильно помогает во время **разработки**, но **не следует** использовать её при **реальной работе** приложения.
|
||||
|
||||
///
|
||||
|
||||
## Hypercorn с Trio
|
||||
У каждого альтернативного ASGI‑сервера будет похожая команда; подробнее см. в их документации.
|
||||
|
||||
Starlette и **FastAPI** основаны на <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a>, которая делает их совместимыми как с <a href="https://docs.python.org/3/library/asyncio-task.html" class="external-link" target="_blank">asyncio</a> - стандартной библиотекой Python, так и с <a href="https://trio.readthedocs.io/en/stable/" class="external-link" target="_blank">Trio</a>.
|
||||
/// warning | Предупреждение
|
||||
|
||||
Uvicorn и другие серверы поддерживают опцию `--reload`, полезную в период разработки.
|
||||
|
||||
Тем не менее Uvicorn совместим только с asyncio и обычно используется совместно с <a href="https://github.com/MagicStack/uvloop" class="external-link" target="_blank">`uvloop`</a>, высокопроизводительной заменой `asyncio`.
|
||||
Опция `--reload` потребляет значительно больше ресурсов, менее стабильна и т.п.
|
||||
|
||||
Но если вы хотите использовать **Trio** напрямую, то можете воспользоваться **Hypercorn**, так как они совместимы. ✨
|
||||
Она сильно помогает во время **разработки**, но в **продакшн** её использовать **не следует**.
|
||||
|
||||
### Установка Hypercorn с Trio
|
||||
///
|
||||
|
||||
Для начала, вам нужно установить Hypercorn с поддержкой Trio:
|
||||
## Концепции развёртывания { #deployment-concepts }
|
||||
|
||||
<div class="termy">
|
||||
В этих примерах серверная программа (например, Uvicorn) запускает **один процесс**, слушающий все IP‑адреса (`0.0.0.0`) на заранее заданном порту (например, `80`).
|
||||
|
||||
```console
|
||||
$ pip install "hypercorn[trio]"
|
||||
---> 100%
|
||||
```
|
||||
Это базовая идея. Но, вероятно, вам понадобится позаботиться и о некоторых дополнительных вещах, например:
|
||||
|
||||
</div>
|
||||
* Безопасность — HTTPS
|
||||
* Запуск при старте системы
|
||||
* Перезапуски
|
||||
* Репликация (количество запущенных процессов)
|
||||
* Память
|
||||
* Предварительные шаги перед запуском
|
||||
|
||||
### Запуск с Trio
|
||||
|
||||
Далее запустите Hypercorn с опцией `--worker-class` и аргументом `trio`:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ hypercorn main:app --worker-class trio
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
Hypercorn, в свою очередь, запустит ваше приложение использующее Trio.
|
||||
|
||||
Таким образом, вы сможете использовать Trio в своём приложении. Но лучше использовать AnyIO, для сохранения совместимости и с Trio, и с asyncio. 🎉
|
||||
|
||||
## Концепции развёртывания
|
||||
|
||||
В вышеприведённых примерах серверные программы (например Uvicorn) запускали только **один процесс**, принимающий входящие запросы с любого IP (на это указывал аргумент `0.0.0.0`) на определённый порт (в примерах мы указывали порт `80`).
|
||||
|
||||
Это основная идея. Но возможно, вы озаботитесь добавлением дополнительных возможностей, таких как:
|
||||
|
||||
* Использование более безопасного протокола HTTPS
|
||||
* Настройки запуска приложения
|
||||
* Перезагрузка приложения
|
||||
* Запуск нескольких экземпляров приложения
|
||||
* Управление памятью
|
||||
* Использование перечисленных функций перед запуском приложения.
|
||||
|
||||
Я расскажу вам больше о каждой из этих концепций в следующих главах, с конкретными примерами стратегий работы с ними. 🚀
|
||||
В следующих главах я расскажу подробнее про каждую из этих концепций, о том, как о них думать, и приведу конкретные примеры со стратегиями, как с ними работать. 🚀
|
||||
|
||||
@@ -1,42 +1,42 @@
|
||||
# О версиях FastAPI
|
||||
# О версиях FastAPI { #about-fastapi-versions }
|
||||
|
||||
**FastAPI** уже используется в продакшене во многих приложениях и системах. Покрытие тестами поддерживается на уровне 100%. Однако его разработка все еще продолжается.
|
||||
**FastAPI** уже используется в продакшене во многих приложениях и системах. Покрытие тестами поддерживается на уровне 100%. Но его разработка всё ещё движется быстрыми темпами.
|
||||
|
||||
Часто добавляются новые функции, регулярно исправляются баги, код продолжает постоянно совершенствоваться.
|
||||
|
||||
По указанным причинам текущие версии до сих пор `0.x.x`. Это говорит о том, что каждая версия может содержать обратно несовместимые изменения, следуя <a href="https://semver.org/" class="external-link" target="_blank">соглашению о Семантическом Версионировании</a>.
|
||||
По указанным причинам текущие версии до сих пор `0.x.x`. Это говорит о том, что каждая версия может содержать обратно несовместимые изменения, следуя <a href="https://semver.org/" class="external-link" target="_blank">Семантическому версионированию</a>.
|
||||
|
||||
Уже сейчас вы можете создавать приложения в продакшене, используя **FastAPI** (и скорее всего так и делаете), главное убедиться в том, что вы используете версию, которая корректно работает с вашим кодом.
|
||||
Уже сейчас вы можете создавать приложения в продакшене, используя **FastAPI** (и скорее всего так и делаете), главное убедиться в том, что вы используете версию, которая корректно работает с вашим кодом.
|
||||
|
||||
## Закрепите вашу версию `fastapi`
|
||||
## Закрепите вашу версию `fastapi` { #pin-your-fastapi-version }
|
||||
|
||||
Первым делом вам следует "закрепить" конкретную последнюю используемую версию **FastAPI**, которая корректно работает с вашим приложением.
|
||||
|
||||
Например, в своём приложении вы используете версию `0.45.0`.
|
||||
Например, в своём приложении вы используете версию `0.112.0`.
|
||||
|
||||
Если вы используете файл `requirements.txt`, вы можете указать версию следующим способом:
|
||||
|
||||
```txt
|
||||
fastapi==0.45.0
|
||||
fastapi[standard]==0.112.0
|
||||
```
|
||||
|
||||
это означает, что вы будете использовать именно версию `0.45.0`.
|
||||
это означает, что вы будете использовать именно версию `0.112.0`.
|
||||
|
||||
Или вы можете закрепить версию следующим способом:
|
||||
|
||||
```txt
|
||||
fastapi>=0.45.0,<0.46.0
|
||||
fastapi[standard]>=0.112.0,<0.113.0
|
||||
```
|
||||
|
||||
это значит, что вы используете версии `0.45.0` или выше, но меньше чем `0.46.0`. Например, версия `0.45.2` все еще будет подходить.
|
||||
это значит, что вы используете версии `0.112.0` или выше, но меньше чем `0.113.0`. Например, версия `0.112.2` всё ещё будет подходить.
|
||||
|
||||
Если вы используете любой другой инструмент для управления зависимостями, например Poetry, Pipenv или др., у них у всех имеется способ определения специфической версии для ваших пакетов.
|
||||
Если вы используете любой другой инструмент для управления установками/зависимостями, например `uv`, Poetry, Pipenv или др., у них у всех имеется способ определения специфической версии для ваших пакетов.
|
||||
|
||||
## Доступные версии
|
||||
## Доступные версии { #available-versions }
|
||||
|
||||
Вы можете посмотреть доступные версии (например, проверить последнюю на данный момент) в [примечаниях к выпуску](../release-notes.md){.internal-link target=_blank}.
|
||||
Вы можете посмотреть доступные версии (например, проверить последнюю на данный момент) в [Примечаниях к выпуску](../release-notes.md){.internal-link target=_blank}.
|
||||
|
||||
## О версиях
|
||||
## О версиях { #about-versions }
|
||||
|
||||
Следуя соглашению о Семантическом Версионировании, любые версии ниже `1.0.0` потенциально могут добавить обратно несовместимые изменения.
|
||||
|
||||
@@ -44,7 +44,7 @@ FastAPI следует соглашению в том, что любые изм
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
"ПАТЧ" - это последнее число. Например, в `0.2.3`, ПАТЧ-версия - это `3`.
|
||||
"ПАТЧ" — это последнее число. Например, в `0.2.3`, ПАТЧ-версия — это `3`.
|
||||
|
||||
///
|
||||
|
||||
@@ -58,11 +58,11 @@ fastapi>=0.45.0,<0.46.0
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
"МИНОРНАЯ" версия - это число в середине. Например, в `0.2.3` МИНОРНАЯ версия - это `2`.
|
||||
"МИНОРНАЯ" версия — это число в середине. Например, в `0.2.3` МИНОРНАЯ версия — это `2`.
|
||||
|
||||
///
|
||||
|
||||
## Обновление версий FastAPI
|
||||
## Обновление версий FastAPI { #upgrading-the-fastapi-versions }
|
||||
|
||||
Вам следует добавить тесты для вашего приложения.
|
||||
|
||||
@@ -70,9 +70,9 @@ fastapi>=0.45.0,<0.46.0
|
||||
|
||||
После создания тестов вы можете обновить свою версию **FastAPI** до более новой. После этого следует убедиться, что ваш код работает корректно, запустив тесты.
|
||||
|
||||
Если все работает корректно, или после внесения необходимых изменений все ваши тесты проходят, только тогда вы можете закрепить вашу новую версию `fastapi`.
|
||||
Если всё работает корректно, или после внесения необходимых изменений все ваши тесты проходят, только тогда вы можете закрепить вашу новую версию `fastapi`.
|
||||
|
||||
## О Starlette
|
||||
## О Starlette { #about-starlette }
|
||||
|
||||
Не следует закреплять версию `starlette`.
|
||||
|
||||
@@ -80,14 +80,14 @@ fastapi>=0.45.0,<0.46.0
|
||||
|
||||
Так что решение об используемой версии Starlette, вы можете оставить **FastAPI**.
|
||||
|
||||
## О Pydantic
|
||||
## О Pydantic { #about-pydantic }
|
||||
|
||||
Pydantic включает свои собственные тесты для **FastAPI**, так что новые версии Pydantic (выше `1.0.0`) всегда совместимы с FastAPI.
|
||||
|
||||
Вы можете закрепить любую версию Pydantic, которая вам подходит, выше `1.0.0` и ниже `2.0.0`.
|
||||
Вы можете закрепить любую версию Pydantic, которая вам подходит, выше `1.0.0`.
|
||||
|
||||
Например:
|
||||
|
||||
```txt
|
||||
pydantic>=1.2.0,<2.0.0
|
||||
pydantic>=2.7.0,<3.0.0
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Переменные окружения
|
||||
# Переменные окружения { #environment-variables }
|
||||
|
||||
/// tip
|
||||
/// tip | Совет
|
||||
|
||||
Если вы уже знаете, что такое «переменные окружения» и как их использовать, можете пропустить это.
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
Переменные окружения могут быть полезны для работы с **настройками** приложений, как часть **установки** Python и т.д.
|
||||
|
||||
## Создание и использование переменных окружения
|
||||
## Создание и использование переменных окружения { #create-and-use-env-vars }
|
||||
|
||||
Можно **создавать** и использовать переменные окружения в **оболочке (терминале)**, не прибегая к помощи Python:
|
||||
|
||||
@@ -50,7 +50,7 @@ Hello Wade Wilson
|
||||
|
||||
////
|
||||
|
||||
## Чтение переменных окружения в python
|
||||
## Чтение переменных окружения в python { #read-env-vars-in-python }
|
||||
|
||||
Так же существует возможность создания переменных окружения **вне** Python, в терминале (или любым другим способом), а затем **чтения их в Python**.
|
||||
|
||||
@@ -63,11 +63,12 @@ name = os.getenv("MY_NAME", "World")
|
||||
print(f"Hello {name} from Python")
|
||||
```
|
||||
|
||||
/// tip
|
||||
/// tip | Совет
|
||||
|
||||
Второй аргумент <a href=«https://docs.python.org/3.8/library/os.html#os.getenv» class=«external-link» target=«_blank»>`os.getenv()`</a> - это возвращаемое по умолчанию значение.
|
||||
Второй аргумент <a href="https://docs.python.org/3.8/library/os.html#os.getenv" class="external-link" target="_blank">`os.getenv()`</a> - это возвращаемое по умолчанию значение.
|
||||
|
||||
Если значение не указано, то по умолчанию оно равно `None`. В данном случае мы указываем `«World»` в качестве значения по умолчанию.
|
||||
|
||||
///
|
||||
|
||||
Затем можно запустить эту программу на Python:
|
||||
@@ -150,13 +151,13 @@ Hello World from Python
|
||||
|
||||
</div>
|
||||
|
||||
/// tip
|
||||
/// tip | Совет
|
||||
|
||||
Подробнее об этом можно прочитать на сайте <a href="https://12factor.net/config" class="external-link" target="_blank">The Twelve-Factor App: Config</a>.
|
||||
|
||||
///
|
||||
|
||||
## Типизация и Валидация
|
||||
## Типизация и Валидация { #types-and-validation }
|
||||
|
||||
Эти переменные окружения могут работать только с **текстовыми строками**, поскольку они являются внешними по отношению к Python и должны быть совместимы с другими программами и остальной системой (и даже с различными операционными системами, такими как Linux, Windows, macOS).
|
||||
|
||||
@@ -164,7 +165,7 @@ Hello World from Python
|
||||
|
||||
Подробнее об использовании переменных окружения для работы с **настройками приложения** вы узнаете в [Расширенное руководство пользователя - Настройки и переменные среды](./advanced/settings.md){.internal-link target=_blank}.
|
||||
|
||||
## Переменная окружения `PATH`
|
||||
## Переменная окружения `PATH` { #path-environment-variable }
|
||||
|
||||
Существует **специальная** переменная окружения **`PATH`**, которая используется операционными системами (Linux, macOS, Windows) для поиска программ для запуска.
|
||||
|
||||
@@ -208,7 +209,7 @@ C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System3
|
||||
|
||||
Если она ее находит, то **использует ее**. В противном случае она продолжает искать в **других каталогах**.
|
||||
|
||||
### Установка Python и обновление `PATH`
|
||||
### Установка Python и обновление `PATH` { #installing-python-and-updating-the-path }
|
||||
|
||||
При установке Python вас могут спросить, нужно ли обновить переменную окружения `PATH`.
|
||||
|
||||
@@ -286,7 +287,7 @@ $ C:\opt\custompython\bin\python
|
||||
|
||||
Эта информация будет полезна при изучении [Виртуальных окружений](virtual-environments.md){.internal-link target=_blank}.
|
||||
|
||||
## Вывод
|
||||
## Вывод { #conclusion }
|
||||
|
||||
Благодаря этому вы должны иметь базовое представление о том, что такое **переменные окружения** и как использовать их в Python.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# FastAPI CLI
|
||||
# FastAPI CLI { #fastapi-cli }
|
||||
|
||||
**FastAPI CLI** это программа командной строки, которую вы можете использовать для запуска вашего FastAPI приложения, для управления FastAPI-проектом, а также для многих других вещей.
|
||||
|
||||
@@ -50,26 +50,26 @@ $ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:solid
|
||||
|
||||
FastAPI CLI берет путь к вашей Python-программе (напр. `main.py`) и автоматически находит объект `FastAPI` (обычно это `app`), затем определяет правильный процесс импорта и запускает сервер приложения.
|
||||
|
||||
Для работы в production окружении вместо `fastapi dev` нужно использовать `fastapi run`. 🚀
|
||||
Для работы в режиме продакшн вместо `fastapi dev` нужно использовать `fastapi run`. 🚀
|
||||
|
||||
Внутри **FastAPI CLI** используется <a href="https://www.uvicorn.org" class="external-link" target="_blank">Uvicorn</a>, высокопроизводительный, готовый к работе в production сервер ASGI. 😎
|
||||
Внутри **FastAPI CLI** используется <a href="https://www.uvicorn.org" class="external-link" target="_blank">Uvicorn</a>, высокопроизводительный, готовый к работе в продакшне ASGI-сервер. 😎
|
||||
|
||||
## `fastapi dev`
|
||||
## `fastapi dev` { #fastapi-dev }
|
||||
|
||||
Вызов `fastapi dev` запускает режим разработки.
|
||||
|
||||
По умолчанию включена автоматическая перезагрузка (**auto-reload**), благодаря этому при изменении кода происходит перезагрузка сервера приложения. Эта установка требует значительных ресурсов и делает систему менее стабильной. Используйте её только при разработке. Приложение слушает входящие подключения на IP `127.0.0.1`. Это IP адрес вашей машины, предназначенный для внутренних коммуникаций (`localhost`).
|
||||
По умолчанию включена авто-перезагрузка (**auto-reload**), благодаря этому при изменении кода происходит перезагрузка сервера приложения. Эта установка требует значительных ресурсов и делает систему менее стабильной. Используйте её только при разработке. Приложение слушает входящие подключения на IP `127.0.0.1`. Это IP адрес вашей машины, предназначенный для внутренних коммуникаций (`localhost`).
|
||||
|
||||
## `fastapi run`
|
||||
## `fastapi run` { #fastapi-run }
|
||||
|
||||
Вызов `fastapi run` по умолчанию запускает FastAPI в режиме production.
|
||||
Вызов `fastapi run` по умолчанию запускает FastAPI в режиме продакшн.
|
||||
|
||||
По умолчанию функция перезагрузки **auto-reload** отключена. Приложение слушает входящие подключения на IP `0.0.0.0`, т.е. на всех доступных адресах компьютера. Таким образом, приложение будет находиться в публичном доступе для любого, кто может подсоединиться к вашей машине. Продуктовые приложения запускаются именно так, например, с помощью контейнеров.
|
||||
По умолчанию авто-перезагрузка (**auto-reload**) отключена. Приложение слушает входящие подключения на IP `0.0.0.0`, т.е. на всех доступных адресах компьютера. Таким образом, приложение будет находиться в публичном доступе для любого, кто может подсоединиться к вашей машине. Продуктовые приложения запускаются именно так, например, с помощью контейнеров.
|
||||
|
||||
В большинстве случаев вы будете (и должны) использовать прокси-сервер ("termination proxy"), который будет поддерживать HTTPS поверх вашего приложения. Всё будет зависеть от того, как вы развертываете приложение: за вас это либо сделает ваш провайдер, либо вам придется сделать настройки самостоятельно.
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Вы можете больше узнать об этом в документации по развертыванию приложений [deployment documentation](deployment/index.md){.internal-link target=_blank}.
|
||||
Вы можете больше узнать об этом в [документации по развертыванию](deployment/index.md){.internal-link target=_blank}.
|
||||
|
||||
///
|
||||
|
||||
@@ -1,23 +1,21 @@
|
||||
# Основные свойства
|
||||
# Возможности { #features }
|
||||
|
||||
## Основные свойства FastAPI
|
||||
## Возможности FastAPI { #fastapi-features }
|
||||
|
||||
**FastAPI** предлагает вам следующее:
|
||||
|
||||
### Использование открытых стандартов
|
||||
### Основано на открытых стандартах { #based-on-open-standards }
|
||||
|
||||
* <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank"><strong>OpenAPI</strong></a> для создания API, включая объявления <abbr title="также известных, как HTTP-методы, такие, как: POST, GET, PUT, DELETE">операций</abbr> <abbr title="известные как: эндпоинты, маршруты, 'ручки' и т.п.">пути</abbr>, параметров, тела запроса, безопасности и т.д.
|
||||
|
||||
|
||||
* Автоматическое документирование моделей данных в соответствии с <a href="https://json-schema.org/" class="external-link" target="_blank"><strong>JSON Schema</strong></a> (так как спецификация OpenAPI сама основана на JSON Schema).
|
||||
* Разработан, придерживаясь этих стандартов, после тщательного их изучения. Эти стандарты изначально включены во фреймфорк, а не являются дополнительной надстройкой.
|
||||
* <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank"><strong>OpenAPI</strong></a> для создания API, включая объявления <abbr title="также известные как HTTP-методы, например: POST, GET, PUT, DELETE">операций</abbr> <abbr title="также известен как: эндпоинты, маршруты)">пути</abbr>, параметров, тел запросов, безопасности и т. д.
|
||||
* Автоматическая документация моделей данных с помощью <a href="https://json-schema.org/" class="external-link" target="_blank"><strong>JSON Schema</strong></a> (так как сама спецификация OpenAPI основана на JSON Schema).
|
||||
* Разработан вокруг этих стандартов, после тщательного их изучения. Это не дополнительная надстройка поверх.
|
||||
* Это также позволяет использовать автоматическую **генерацию клиентского кода** на многих языках.
|
||||
|
||||
### Автоматически генерируемая документация
|
||||
### Автоматическая документация { #automatic-docs }
|
||||
|
||||
Интерактивная документация для API и исследования пользовательских веб-интерфейсов. Поскольку этот фреймворк основан на OpenAPI, существует несколько вариантов документирования, 2 из которых включены по умолчанию.
|
||||
Интерактивная документация для API и исследовательские веб-интерфейсы. Поскольку фреймворк основан на OpenAPI, существует несколько вариантов документирования, 2 из них включены по умолчанию.
|
||||
|
||||
* <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank"><strong>Swagger UI</strong></a>, с интерактивным взаимодействием, вызывает и тестирует ваш API прямо из браузера.
|
||||
* <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank"><strong>Swagger UI</strong></a>, с интерактивным исследованием, вызовом и тестированием вашего API прямо из браузера.
|
||||
|
||||

|
||||
|
||||
@@ -25,22 +23,21 @@
|
||||
|
||||

|
||||
|
||||
### Только современный Python
|
||||
### Только современный Python { #just-modern-python }
|
||||
|
||||
Все эти возможности основаны на стандартных **аннотациях типов Python 3.8** (благодаря Pydantic). Не нужно изучать новый синтаксис. Только лишь стандартный современный Python.
|
||||
Все основано на стандартных **аннотациях типов Python** (благодаря Pydantic). Не нужно изучать новый синтаксис. Только стандартный современный Python.
|
||||
|
||||
Если вам нужно освежить знания, как использовать аннотации типов в Python (даже если вы не используете FastAPI), выделите 2 минуты и просмотрите краткое руководство: [Введение в аннотации типов Python¶
|
||||
](python-types.md){.internal-link target=_blank}.
|
||||
Если вам нужно освежить знания о типах в Python (даже если вы не используете FastAPI), выделите 2 минуты и просмотрите краткое руководство: [Типы Python](python-types.md){.internal-link target=_blank}.
|
||||
|
||||
Вы пишете на стандартном Python с аннотациями типов:
|
||||
Вы пишете стандартный Python с типами:
|
||||
|
||||
```Python
|
||||
from datetime import date
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
# Объявляем параметр user_id с типом `str`
|
||||
# и получаем поддержку редактора внутри функции
|
||||
# Объявляем параметр как `str`
|
||||
# и получаем поддержку редактора кода внутри функции
|
||||
def main(user_id: str):
|
||||
return user_id
|
||||
|
||||
@@ -70,17 +67,17 @@ my_second_user: User = User(**second_user_data)
|
||||
|
||||
`**second_user_data` означает:
|
||||
|
||||
Передать ключи и значения словаря `second_user_data`, в качестве аргументов типа "ключ-значение", это эквивалентно: `User(id=4, name="Mary", joined="2018-11-30")` .
|
||||
Передать ключи и значения словаря `second_user_data` в качестве аргументов "ключ-значение", эквивалентно: `User(id=4, name="Mary", joined="2018-11-30")`
|
||||
|
||||
///
|
||||
|
||||
### Поддержка редакторов (IDE)
|
||||
### Поддержка редакторов (IDE) { #editor-support }
|
||||
|
||||
Весь фреймворк был продуман так, чтобы быть простым и интуитивно понятным в использовании, все решения были проверены на множестве редакторов еще до начала разработки, чтобы обеспечить наилучшие условия при написании кода.
|
||||
|
||||
В опросе Python-разработчиков было выяснено, <a href="https://www.jetbrains.com/research/python-developers-survey-2017/#tools-and-features" class="external-link" target="_blank">что наиболее часто используемой функцией редакторов, является "автодополнение"</a>.
|
||||
В опросах Python‑разработчиков видно, <a href="https://www.jetbrains.com/research/python-developers-survey-2017/#tools-and-features" class="external-link" target="_blank">что одной из самых часто используемых функций является «автозавершение»</a>.
|
||||
|
||||
Вся структура **FastAPI** основана на удовлетворении этой возможности. Автодополнение работает везде.
|
||||
Вся структура **FastAPI** основана на удовлетворении этой возможности. Автозавершение работает везде.
|
||||
|
||||
Вам редко нужно будет возвращаться к документации.
|
||||
|
||||
@@ -94,23 +91,23 @@ my_second_user: User = User(**second_user_data)
|
||||
|
||||

|
||||
|
||||
Вы будете получать автодополнение кода даже там, где вы считали это невозможным раньше.
|
||||
Как пример, ключ `price` внутри тела JSON (который может быть вложенным), приходящего в запросе.
|
||||
Вы будете получать автозавершение кода даже там, где вы считали это невозможным раньше. Как пример, ключ `price` внутри тела JSON (который может быть вложенным), приходящего в запросе.
|
||||
|
||||
Больше никаких неправильных имён ключей, метания по документации или прокручивания кода вверх и вниз, в попытках узнать - использовали вы ранее `username` или `user_name`.
|
||||
Больше никаких неправильных имён ключей, метания по документации или прокручивания кода вверх и вниз в попытках узнать — использовали вы ранее `username` или `user_name`.
|
||||
|
||||
### Краткость
|
||||
FastAPI имеет продуманные значения **по умолчанию** для всего, с произвольными настройками везде. Все параметры могут быть тонко подстроены так, чтобы делать то, что вам нужно и определять необходимый вам API.
|
||||
### Краткость { #short }
|
||||
|
||||
Но, по умолчанию, всё это **"и так работает"**.
|
||||
FastAPI имеет продуманные значения **по умолчанию** для всего, с опциональными настройками везде. Все параметры могут быть тонко подстроены так, чтобы делать то, что вам нужно, и определять необходимый вам API.
|
||||
|
||||
### Проверка значений
|
||||
Но по умолчанию всё **«просто работает»**.
|
||||
|
||||
* Проверка значений для большинства (или всех?) **типов данных** Python, включая:
|
||||
### Проверка значений { #validation }
|
||||
|
||||
* Проверка значений для большинства (или всех?) **типов данных** Python, включая:
|
||||
* Объекты JSON (`dict`).
|
||||
* Массивы JSON (`list`) с установленными типами элементов.
|
||||
* Массив JSON (`list`) с определёнными типами элементов.
|
||||
* Строковые (`str`) поля с ограничением минимальной и максимальной длины.
|
||||
* Числа (`int`, `float`) с минимальными и максимальными значениями и т.п.
|
||||
* Числа (`int`, `float`) с минимальными и максимальными значениями и т. п.
|
||||
|
||||
* Проверка для более экзотических типов, таких как:
|
||||
* URL.
|
||||
@@ -118,11 +115,11 @@ FastAPI имеет продуманные значения **по умолчан
|
||||
* UUID.
|
||||
* ...и другие.
|
||||
|
||||
Все проверки обрабатываются хорошо зарекомендовавшим себя и надежным **Pydantic**.
|
||||
Все проверки обрабатываются хорошо зарекомендовавшим себя и надёжным **Pydantic**.
|
||||
|
||||
### Безопасность и аутентификация
|
||||
### Безопасность и аутентификация { #security-and-authentication }
|
||||
|
||||
Встроеные функции безопасности и аутентификации. Без каких-либо компромиссов с базами данных или моделями данных.
|
||||
Встроенные функции безопасности и аутентификации. Без каких‑либо компромиссов с базами данных или моделями данных.
|
||||
|
||||
Все схемы безопасности, определённые в OpenAPI, включая:
|
||||
|
||||
@@ -137,68 +134,68 @@ FastAPI имеет продуманные значения **по умолчан
|
||||
|
||||
Все инструменты и компоненты спроектированы для многократного использования и легко интегрируются с вашими системами, хранилищами данных, реляционными и NoSQL базами данных и т. д.
|
||||
|
||||
### Внедрение зависимостей
|
||||
### Внедрение зависимостей { #dependency-injection }
|
||||
|
||||
FastAPI включает в себя чрезвычайно простую в использовании, но чрезвычайно мощную систему <abbr title='известную как: "components", "resources", "services", "providers"'><strong>Внедрения зависимостей</strong></abbr>.
|
||||
|
||||
* Даже зависимости могут иметь зависимости, создавая иерархию или **"графы" зависимостей**.
|
||||
* Даже зависимости могут иметь зависимости, создавая иерархию или **«граф» зависимостей**.
|
||||
* Всё **автоматически обрабатывается** фреймворком.
|
||||
* Все зависимости могут запрашивать данные из запросов и **дополнять операции пути** ограничениями и автоматической документацией.
|
||||
* **Автоматическая проверка** даже для параметров *операций пути*, определенных в зависимостях.
|
||||
* Поддержка сложных систем аутентификации пользователей, **соединений с базами данных** и т.д.
|
||||
* **Никаких компромиссов** с базами данных, интерфейсами и т.д. Но легкая интеграция со всеми ними.
|
||||
* **Автоматическая проверка** даже для параметров *операций пути*, определённых в зависимостях.
|
||||
* Поддержка сложных систем аутентификации пользователей, **соединений с базами данных** и т. д.
|
||||
* **Никаких компромиссов** с базами данных, интерфейсами и т. д. Но при этом — лёгкая интеграция со всеми ними.
|
||||
|
||||
### Нет ограничений на "Плагины"
|
||||
### Нет ограничений на "Плагины" { #unlimited-plug-ins }
|
||||
|
||||
Или, другими словами, нет сложностей с ними, импортируйте и используйте нужный вам код.
|
||||
Или, другими словами, нет необходимости в них — просто импортируйте и используйте нужный вам код.
|
||||
|
||||
Любая интеграция разработана настолько простой в использовании (с зависимостями), что вы можете создать "плагин" для своего приложения в пару строк кода, используя ту же структуру и синтаксис, что и для ваших *операций пути*.
|
||||
Любая интеграция разработана настолько простой в использовании (с зависимостями), что вы можете создать «плагин» для своего приложения в пару строк кода, используя ту же структуру и синтаксис, что и для ваших *операций пути*.
|
||||
|
||||
### Проверен
|
||||
### Проверен { #tested }
|
||||
|
||||
* 100% <abbr title="Количество автоматически проверямого кода">покрытие тестами</abbr>.
|
||||
* 100% <abbr title="Аннотации типов Python, благодаря которым ваш редактор и другие инструменты могут обеспечить вам лучшую поддержку">аннотирование типов</abbr> в кодовой базе.
|
||||
* Используется в реально работающих приложениях.
|
||||
* 100% <abbr title="Количество автоматически проверяемого кода">покрытие тестами</abbr>.
|
||||
* 100% <abbr title="Аннотации типов Python, благодаря которым ваш редактор и внешние инструменты могут обеспечить вам лучшую поддержку">аннотирование типов</abbr> в кодовой базе.
|
||||
* Используется в продакшн‑приложениях.
|
||||
|
||||
## Основные свойства Starlette
|
||||
## Возможности Starlette { #starlette-features }
|
||||
|
||||
**FastAPI** основан на <a href="https://www.starlette.io/" class="external-link" target="_blank"><strong>Starlette</strong></a> и полностью совместим с ним. Так что, любой дополнительный код Starlette, который у вас есть, будет также работать.
|
||||
**FastAPI** основан на <a href="https://www.starlette.io/" class="external-link" target="_blank"><strong>Starlette</strong></a> и полностью совместим с ним. Так что любой дополнительный код Starlette, который у вас есть, также будет работать.
|
||||
|
||||
На самом деле, `FastAPI` - это класс, унаследованный от `Starlette`. Таким образом, если вы уже знаете или используете Starlette, большая часть функционала будет работать так же.
|
||||
На самом деле, `FastAPI` — это подкласс `Starlette`. Таким образом, если вы уже знаете или используете Starlette, большая часть функционала будет работать так же.
|
||||
|
||||
С **FastAPI** вы получаете все возможности **Starlette** (так как FastAPI это всего лишь Starlette на стероидах):
|
||||
С **FastAPI** вы получаете все возможности **Starlette** (так как FastAPI — это всего лишь Starlette на стероидах):
|
||||
|
||||
* Серьёзно впечатляющая производительность. Это <a href="https://github.com/encode/starlette#performance" class="external-link" target="_blank">один из самых быстрых фреймворков на Python</a>, наравне с приложениями использующими **NodeJS** или **Go**.
|
||||
* Серьёзно впечатляющая производительность. Это <a href="https://github.com/encode/starlette#performance" class="external-link" target="_blank">один из самых быстрых фреймворков на Python, наравне с **NodeJS** и **Go**</a>.
|
||||
* Поддержка **WebSocket**.
|
||||
* Фоновые задачи для процессов.
|
||||
* Фоновые задачи в том же процессе.
|
||||
* События запуска и выключения.
|
||||
* Тестовый клиент построен на библиотеке HTTPX.
|
||||
* Тестовый клиент построен на HTTPX.
|
||||
* **CORS**, GZip, статические файлы, потоковые ответы.
|
||||
* Поддержка **сессий и cookie**.
|
||||
* 100% покрытие тестами.
|
||||
* 100% аннотирование типов в кодовой базе.
|
||||
|
||||
## Особенности и возможности Pydantic
|
||||
## Возможности Pydantic { #pydantic-features }
|
||||
|
||||
**FastAPI** основан на <a href="https://docs.pydantic.dev/" class="external-link" target="_blank"><strong>Pydantic</strong></a> и полностью совместим с ним. Так что, любой дополнительный код Pydantic, который у вас есть, будет также работать.
|
||||
**FastAPI** полностью совместим с (и основан на) <a href="https://docs.pydantic.dev/" class="external-link" target="_blank"><strong>Pydantic</strong></a>. Поэтому любой дополнительный код Pydantic, который у вас есть, также будет работать.
|
||||
|
||||
Включая внешние библиотеки, также основанные на Pydantic, такие как: <abbr title="Object-Relational Mapper">ORM'ы</abbr>, <abbr title="Object-Document Mapper">ODM'ы</abbr> для баз данных.
|
||||
Включая внешние библиотеки, также основанные на Pydantic, такие как <abbr title="Object-Relational Mapper">ORM</abbr>’ы, <abbr title="Object-Document Mapper">ODM</abbr>’ы для баз данных.
|
||||
|
||||
Это также означает, что во многих случаях вы можете передавать тот же объект, который получили из запроса, **непосредственно в базу данных**, так как всё проверяется автоматически.
|
||||
|
||||
И наоборот, во многих случаях вы можете просто передать объект, полученный из базы данных, **непосредственно клиенту**.
|
||||
|
||||
С **FastAPI** вы получаете все возможности **Pydantic** (так как, FastAPI основан на Pydantic, для обработки данных):
|
||||
С **FastAPI** вы получаете все возможности **Pydantic** (так как FastAPI основан на Pydantic для обработки данных):
|
||||
|
||||
* **Никакой нервотрёпки** :
|
||||
* Не нужно изучать новых схем в микроязыках.
|
||||
* Если вы знаете аннотации типов в Python, вы знаете, как использовать Pydantic.
|
||||
* Прекрасно сочетается с вашими **<abbr title="Интегрированное окружение для разработки, похожее на текстовый редактор">IDE</abbr>/<abbr title="программа проверяющая ошибки в коде">linter</abbr>/мозгом**:
|
||||
* Потому что структуры данных pydantic - это всего лишь экземпляры классов, определённых вами. Автодополнение, проверка кода, mypy и ваша интуиция - всё будет работать с вашими проверенными данными.
|
||||
* Проверка **сложных структур**:
|
||||
* Использование иерархических моделей Pydantic; `List`, `Dict` и т.п. из модуля `typing` (входит в стандартную библиотеку Python).
|
||||
* Валидаторы позволяют четко и легко определять, проверять и документировать сложные схемы данных в виде JSON Schema.
|
||||
* У вас могут быть глубоко **вложенные объекты JSON** и все они будут проверены и аннотированы.
|
||||
* **Никакой нервотрёпки**:
|
||||
* Не нужно изучать новые схемы в микроязыках.
|
||||
* Если вы знаете типы в Python, вы знаете, как использовать Pydantic.
|
||||
* Прекрасно сочетается с вашим **<abbr title="Integrated Development Environment - Интегрированная среда разработки: попросту «редактора кода»">IDE</abbr>/<abbr title="Программа, проверяющая ошибки в коде">linter</abbr>/мозгом**:
|
||||
* Потому что структуры данных pydantic — это всего лишь экземпляры классов, определённых вами; автозавершение, проверка кода, mypy и ваша интуиция — всё будет работать с вашими валидированными данными.
|
||||
* Валидация **сложных структур**:
|
||||
* Использование иерархических моделей Pydantic; `List`, `Dict` и т. п. из модуля `typing` (входит в стандартную библиотеку Python).
|
||||
* Валидаторы позволяют чётко и легко определять, проверять и документировать сложные схемы данных в виде JSON Schema.
|
||||
* У вас могут быть глубоко **вложенные объекты JSON**, и все они будут проверены и аннотированы.
|
||||
* **Расширяемость**:
|
||||
* Pydantic позволяет определять пользовательские типы данных или расширять проверку методами модели, с помощью проверочных декораторов.
|
||||
* Pydantic позволяет определять пользовательские типы данных или расширять проверку методами модели с помощью декораторов валидаторов.
|
||||
* 100% покрытие тестами.
|
||||
|
||||
@@ -1,261 +1,255 @@
|
||||
# Помочь FastAPI - Получить помощь
|
||||
# Помочь FastAPI - Получить помощь { #help-fastapi-get-help }
|
||||
|
||||
Нравится ли Вам **FastAPI**?
|
||||
|
||||
Хотели бы Вы помочь FastAPI, его пользователям и автору?
|
||||
Хотели бы Вы помочь FastAPI, другим пользователям и автору?
|
||||
|
||||
Может быть у Вас возникли трудности с **FastAPI** и Вам нужна помощь?
|
||||
Или Вы хотите получить помощь по **FastAPI**?
|
||||
|
||||
Есть несколько очень простых способов оказания помощи (иногда достаточно всего лишь одного или двух кликов).
|
||||
Есть несколько очень простых способов помочь (иногда достаточно всего лишь одного-двух кликов).
|
||||
|
||||
И также есть несколько способов получить помощь.
|
||||
|
||||
## Подписаться на новостную рассылку
|
||||
## Подписаться на новостную рассылку { #subscribe-to-the-newsletter }
|
||||
|
||||
Вы можете подписаться на редкую [новостную рассылку **FastAPI и его друзья**](newsletter.md){.internal-link target=_blank} и быть в курсе о:
|
||||
|
||||
* Новостях о FastAPI и его друзьях 🚀
|
||||
* Руководствах 📝
|
||||
* Возможностях ✨
|
||||
* Исправлениях 🚨
|
||||
* Ломающих изменениях 🚨
|
||||
* Подсказках и хитростях ✅
|
||||
|
||||
## Подписаться на FastAPI в X (Twitter)
|
||||
## Подписаться на FastAPI в X (Twitter) { #follow-fastapi-on-x-twitter }
|
||||
|
||||
<a href="https://x.com/fastapi" class="external-link" target="_blank">Подписаться на @fastapi в **X (Twitter)**</a> для получения наисвежайших новостей о **FastAPI**. 🐦
|
||||
|
||||
## Добавить **FastAPI** звезду на GitHub
|
||||
## Добавить **FastAPI** звезду на GitHub { #star-fastapi-in-github }
|
||||
|
||||
Вы можете добавить FastAPI "звезду" на GitHub (кликнуть на кнопку звезды в верхнем правом углу экрана): <a href="https://github.com/fastapi/fastapi" class="external-link" target="_blank">https://github.com/fastapi/fastapi</a>. ⭐️
|
||||
Вы можете добавить FastAPI "звезду" на GitHub (кликнув на кнопку звезды в правом верхнем углу): <a href="https://github.com/fastapi/fastapi" class="external-link" target="_blank">https://github.com/fastapi/fastapi</a>. ⭐️
|
||||
|
||||
Чем больше звёзд, тем легче другим пользователям найти нас и увидеть, что проект уже стал полезным для многих.
|
||||
Чем больше звёзд, тем легче другим пользователям найти проект и увидеть, что он уже оказался полезным для многих.
|
||||
|
||||
## Отслеживать свежие выпуски в репозитории на GitHub
|
||||
## Отслеживать свежие выпуски в репозитории на GitHub { #watch-the-github-repository-for-releases }
|
||||
|
||||
Вы можете "отслеживать" FastAPI на GitHub (кликните по кнопке "watch" наверху справа): <a href="https://github.com/fastapi/fastapi" class="external-link" target="_blank">https://github.com/fastapi/fastapi</a>. 👀
|
||||
Вы можете "отслеживать" FastAPI на GitHub (кликнув по кнопке "watch" наверху справа): <a href="https://github.com/fastapi/fastapi" class="external-link" target="_blank">https://github.com/fastapi/fastapi</a>. 👀
|
||||
|
||||
Там же Вы можете указать в настройках - "Releases only".
|
||||
Там же Вы можете выбрать "Releases only".
|
||||
|
||||
С такой настройкой Вы будете получать уведомления на вашу электронную почту каждый раз, когда появится новый релиз (новая версия) **FastAPI** с исправлениями ошибок и новыми возможностями.
|
||||
|
||||
## Связаться с автором
|
||||
## Связаться с автором { #connect-with-the-author }
|
||||
|
||||
Можно связаться со <a href="https://tiangolo.com" class="external-link" target="_blank">мной (Себястьян Рамирез / `tiangolo`)</a>, автором FastAPI.
|
||||
Можно связаться со <a href="https://tiangolo.com" class="external-link" target="_blank">мной (Sebastián Ramírez / `tiangolo`)</a>, автором.
|
||||
|
||||
Вы можете:
|
||||
|
||||
* <a href="https://github.com/tiangolo" class="external-link" target="_blank">Подписаться на меня на **GitHub**</a>.
|
||||
* Посмотреть другие мои проекты с открытым кодом, которые могут быть полезны Вам.
|
||||
* Подписавшись на меня Вы сможете получать уведомления, что я создал новый проект с открытым кодом,.
|
||||
* Подписаться, чтобы видеть, когда я создаю новый проект с открытым кодом.
|
||||
* <a href="https://x.com/tiangolo" class="external-link" target="_blank">Подписаться на меня в **X (Twitter)**</a> или в <a href="https://fosstodon.org/@tiangolo" class="external-link" target="_blank">Mastodon</a>.
|
||||
* Поделиться со мной, как Вы используете FastAPI (я обожаю читать про это).
|
||||
* Получать уведомления, когда я делаю объявления и представляю новые инструменты.
|
||||
* Поделиться со мной, как Вы используете FastAPI (я обожаю это читать).
|
||||
* Узнавать, когда я делаю объявления или выпускаю новые инструменты.
|
||||
* Вы также можете <a href="https://x.com/fastapi" class="external-link" target="_blank">подписаться на @fastapi в X (Twitter)</a> (это отдельный аккаунт).
|
||||
* <a href="https://www.linkedin.com/in/tiangolo/" class="external-link" target="_blank">Подписаться на меня в **Linkedin**</a>.
|
||||
* Получать уведомления, когда я делаю объявления и представляю новые инструменты (правда чаще всего я использую X (Twitter) 🤷♂).
|
||||
* Читать, что я пишу (или подписаться на меня) в <a href="https://dev.to/tiangolo" class="external-link" target="_blank">**Dev.to**</a> или в <a href="https://medium.com/@tiangolo" class="external-link" target="_blank">**Medium**</a>.
|
||||
* Читать другие идеи, статьи и читать об инструментах созданных мной.
|
||||
* Подпишитесь на меня, чтобы прочитать, когда я опубликую что-нибудь новое.
|
||||
* <a href="https://www.linkedin.com/in/tiangolo/" class="external-link" target="_blank">Подписаться на меня в **LinkedIn**</a>.
|
||||
* Узнавать, когда я делаю объявления или выпускаю новые инструменты (хотя чаще я использую X (Twitter) 🤷♂).
|
||||
* Читать, что я пишу (или подписаться на меня) на <a href="https://dev.to/tiangolo" class="external-link" target="_blank">**Dev.to**</a> или <a href="https://medium.com/@tiangolo" class="external-link" target="_blank">**Medium**</a>.
|
||||
* Читать другие идеи, статьи и о созданных мной инструментах.
|
||||
* Подписаться, чтобы читать, когда я публикую что-то новое.
|
||||
|
||||
## Оставить сообщение в X (Twitter) о **FastAPI**
|
||||
## Оставить сообщение в X (Twitter) о **FastAPI** { #tweet-about-fastapi }
|
||||
|
||||
<a href="https://x.com/compose/tweet?text=I'm loving @fastapi because... https://github.com/fastapi/fastapi" class="external-link" target="_blank">Оставьте сообщение в X (Twitter) о **FastAPI**</a> и позвольте мне и другим узнать - почему он Вам нравится. 🎉
|
||||
<a href="https://x.com/compose/tweet?text=I'm loving @fastapi because... https://github.com/fastapi/fastapi" class="external-link" target="_blank">Оставьте сообщение в X (Twitter) о **FastAPI**</a> и позвольте мне и другим узнать, почему он Вам нравится. 🎉
|
||||
|
||||
Я люблю узнавать о том, как **FastAPI** используется, что Вам понравилось в нём, в каких проектах/компаниях Вы используете его и т.п.
|
||||
Я люблю узнавать о том, как **FastAPI** используется, что Вам понравилось в нём, в каких проектах/компаниях Вы его используете и т.д.
|
||||
|
||||
## Оставить голос за FastAPI
|
||||
## Оставить голос за FastAPI { #vote-for-fastapi }
|
||||
|
||||
* <a href="https://www.slant.co/options/34241/~fastapi-review" class="external-link" target="_blank">Голосуйте за **FastAPI** в Slant</a>.
|
||||
* <a href="https://alternativeto.net/software/fastapi/" class="external-link" target="_blank">Голосуйте за **FastAPI** в AlternativeTo</a>.
|
||||
* <a href="https://stackshare.io/pypi-fastapi" class="external-link" target="_blank">Расскажите, как Вы используете **FastAPI** на StackShare</a>.
|
||||
* <a href="https://alternativeto.net/software/fastapi/about/" class="external-link" target="_blank">Голосуйте за **FastAPI** в AlternativeTo</a>.
|
||||
* <a href="https://stackshare.io/pypi-fastapi" class="external-link" target="_blank">Расскажите, что Вы используете **FastAPI** на StackShare</a>.
|
||||
|
||||
## Помочь другим с их проблемами на GitHub
|
||||
## Помочь другим с вопросами на GitHub { #help-others-with-questions-in-github }
|
||||
|
||||
Вы можете посмотреть, какие <a href="https://github.com/fastapi/fastapi/issues" class="external-link" target="_blank">проблемы</a> испытывают другие люди и попытаться помочь им. Чаще всего это вопросы, на которые, весьма вероятно, Вы уже знаете ответ. 🤓
|
||||
Вы можете попробовать помочь другим с их вопросами в:
|
||||
|
||||
Если Вы будете много помогать людям с решением их проблем, Вы можете стать официальным [Экспертом FastAPI](fastapi-people.md#_3){.internal-link target=_blank}. 🎉
|
||||
* <a href="https://github.com/fastapi/fastapi/discussions/categories/questions?discussions_q=category%3AQuestions+is%3Aunanswered" class="external-link" target="_blank">GitHub Discussions</a>
|
||||
* <a href="https://github.com/fastapi/fastapi/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Aquestion+-label%3Aanswered+" class="external-link" target="_blank">GitHub Issues</a>
|
||||
|
||||
Только помните, самое важное при этом - доброта. Столкнувшись с проблемой, люди расстраиваются и часто задают вопросы не лучшим образом, но постарайтесь быть максимально доброжелательным. 🤗
|
||||
Во многих случаях Вы уже можете знать ответы на эти вопросы. 🤓
|
||||
|
||||
Идея сообщества **FastAPI** в том, чтобы быть добродушным и гостеприимными. Не допускайте издевательств или неуважительного поведения по отношению к другим. Мы должны заботиться друг о друге.
|
||||
Если Вы много помогаете людям с их вопросами, Вы станете официальным [Экспертом FastAPI](fastapi-people.md#fastapi-experts){.internal-link target=_blank}. 🎉
|
||||
|
||||
Только помните, самое важное — постарайтесь быть добрыми. Люди приходят со своими разочарованиями и часто задают вопросы не лучшим образом, но постарайтесь, насколько можете, быть доброжелательными. 🤗
|
||||
|
||||
Идея сообщества **FastAPI** — быть доброжелательным и гостеприимным. В то же время не допускайте травлю или неуважительное поведение по отношению к другим. Мы должны заботиться друг о друге.
|
||||
|
||||
---
|
||||
|
||||
Как помочь другим с их проблемами:
|
||||
Как помочь другим с вопросами (в обсуждениях или Issues):
|
||||
|
||||
### Понять вопрос
|
||||
### Понять вопрос { #understand-the-question }
|
||||
|
||||
* Удостоверьтесь, что поняли **цель** и обстоятельства случая вопрошающего.
|
||||
* Убедитесь, что поняли **цель** и кейс использования задающего вопрос.
|
||||
|
||||
* Затем проверьте, что вопрос (в подавляющем большинстве - это вопросы) Вам **ясен**.
|
||||
* Затем проверьте, что вопрос (в подавляющем большинстве это вопросы) сформулирован **ясно**.
|
||||
|
||||
* Во многих случаях вопрос касается решения, которое пользователь придумал сам, но может быть и решение **получше**. Если Вы поймёте проблему и обстоятельства случая, то сможете предложить **альтернативное решение**.
|
||||
* Во многих случаях спрашивают о воображаемом решении пользователя, но может быть решение **получше**. Если Вы лучше поймёте проблему и кейс, сможете предложить **альтернативное решение**.
|
||||
|
||||
* Ежели вопрос Вам непонятен, запросите больше **деталей**.
|
||||
* Если вопрос непонятен, запросите больше **деталей**.
|
||||
|
||||
### Воспроизвести проблему
|
||||
### Воспроизвести проблему { #reproduce-the-problem }
|
||||
|
||||
В большинстве случаев есть что-то связанное с **исходным кодом** вопрошающего.
|
||||
В большинстве случаев и вопросов есть что-то связанное с **исходным кодом** автора.
|
||||
|
||||
И во многих случаях будет предоставлен только фрагмент этого кода, которого недостаточно для **воспроизведения проблемы**.
|
||||
Во многих случаях предоставляют только фрагмент кода, но этого недостаточно, чтобы **воспроизвести проблему**.
|
||||
|
||||
* Попросите предоставить <a href="https://stackoverflow.com/help/minimal-reproducible-example" class="external-link" target="_blank">минимальный воспроизводимый пример</a>, который можно **скопировать** и запустить локально дабы увидеть такую же ошибку, или поведение, или лучше понять обстоятельства случая.
|
||||
* Попросите предоставить <a href="https://stackoverflow.com/help/minimal-reproducible-example" class="external-link" target="_blank">минимальный воспроизводимый пример</a>, который Вы сможете **скопировать-вставить** и запустить локально, чтобы увидеть ту же ошибку или поведение, или лучше понять их кейс.
|
||||
|
||||
* Если на Вас нахлынуло великодушие, то можете попытаться **создать похожий пример** самостоятельно, основываясь только на описании проблемы. Но имейте в виду, что это может занять много времени и, возможно, стоит сначала позадавать вопросы для прояснения проблемы.
|
||||
* Если чувствуете себя особенно великодушными, можете попытаться **создать такой пример** сами, основываясь только на описании проблемы. Просто помните, что это может занять много времени, и, возможно, сначала лучше попросить уточнить проблему.
|
||||
|
||||
### Предложить решение
|
||||
### Предложить решение { #suggest-solutions }
|
||||
|
||||
* После того как Вы поняли вопрос, Вы можете дать **ответ**.
|
||||
* После того как Вы поняли вопрос, Вы можете дать возможный **ответ**.
|
||||
|
||||
* Следует понять **основную проблему и обстоятельства случая**, потому что может быть решение лучше, чем то, которое пытались реализовать.
|
||||
* Во многих случаях лучше понять **исходную проблему или кейс**, потому что может существовать способ решить её лучше, чем то, что пытаются сделать.
|
||||
|
||||
### Попросить закрыть проблему
|
||||
### Попросить закрыть { #ask-to-close }
|
||||
|
||||
Если Вам ответили, высоки шансы, что Вам удалось решить проблему, поздравляю, **Вы - герой**! 🦸
|
||||
Если Вам ответили, велика вероятность, что Вы решили их проблему, поздравляю, **Вы — герой**! 🦸
|
||||
|
||||
* В таком случае, если вопрос решён, попросите **закрыть проблему**.
|
||||
* Теперь, если проблема решена, можно попросить их:
|
||||
* В GitHub Discussions: пометить комментарий как **answer** (ответ).
|
||||
* В GitHub Issues: **закрыть** Issue.
|
||||
|
||||
## Отслеживать репозиторий на GitHub
|
||||
## Отслеживать репозиторий на GitHub { #watch-the-github-repository }
|
||||
|
||||
Вы можете "отслеживать" FastAPI на GitHub (кликните по кнопке "watch" наверху справа): <a href="https://github.com/fastapi/fastapi" class="external-link" target="_blank">https://github.com/fastapi/fastapi</a>. 👀
|
||||
Вы можете "отслеживать" FastAPI на GitHub (кликнув по кнопке "watch" наверху справа): <a href="https://github.com/fastapi/fastapi" class="external-link" target="_blank">https://github.com/fastapi/fastapi</a>. 👀
|
||||
|
||||
Если Вы выберете "Watching" вместо "Releases only", то будете получать уведомления когда кто-либо попросит о помощи с решением его проблемы.
|
||||
Если Вы выберете "Watching" вместо "Releases only", то будете получать уведомления, когда кто-либо создаёт новый вопрос или Issue. Вы также можете указать, что хотите получать уведомления только о новых Issues, или обсуждениях, или пулл-реквестах и т.д.
|
||||
|
||||
Тогда Вы можете попробовать решить эту проблему.
|
||||
Тогда Вы можете попробовать помочь им с решением этих вопросов.
|
||||
|
||||
## Запросить помощь с решением проблемы
|
||||
## Задать вопросы { #ask-questions }
|
||||
|
||||
Вы можете <a href="https://github.com/fastapi/fastapi/issues/new/choose" class="external-link" target="_blank">создать новый запрос с просьбой о помощи</a> в репозитории на GitHub, например:
|
||||
Вы можете <a href="https://github.com/fastapi/fastapi/discussions/new?category=questions" class="external-link" target="_blank">создать новый вопрос</a> в репозитории GitHub, например:
|
||||
|
||||
* Задать **вопрос** или попросить помощи в решении **проблемы**.
|
||||
* Предложить новое **улучшение**.
|
||||
* Задать **вопрос** или спросить о **проблеме**.
|
||||
* Предложить новую **возможность**.
|
||||
|
||||
**Заметка**: Если Вы создаёте подобные запросы, то я попрошу Вас также оказывать аналогичную помощь другим. 😉
|
||||
**Заметка**: если Вы это сделаете, то я попрошу Вас также помогать другим. 😉
|
||||
|
||||
## Проверять пул-реквесты
|
||||
## Проверять пулл-реквесты { #review-pull-requests }
|
||||
|
||||
Вы можете помочь мне проверять пул-реквесты других участников.
|
||||
Вы можете помочь мне проверять пулл-реквесты других участников.
|
||||
|
||||
И повторюсь, постарайтесь быть доброжелательным. 🤗
|
||||
И, снова, постарайтесь быть доброжелательными. 🤗
|
||||
|
||||
---
|
||||
|
||||
О том, что нужно иметь в виду при проверке пул-реквестов:
|
||||
О том, что нужно иметь в виду и как проверять пулл-реквест:
|
||||
|
||||
### Понять проблему
|
||||
### Понять проблему { #understand-the-problem }
|
||||
|
||||
* Во-первых, убедитесь, что **поняли проблему**, которую пул-реквест пытается решить. Для этого может потребоваться продолжительное обсуждение.
|
||||
* Во-первых, убедитесь, что **поняли проблему**, которую пулл-реквест пытается решить. Возможно, это обсуждалось более подробно в GitHub Discussion или Issue.
|
||||
|
||||
* Также есть вероятность, что пул-реквест не актуален, так как проблему можно решить **другим путём**. В таком случае Вы можете указать на этот факт.
|
||||
* Также есть вероятность, что пулл-реквест не нужен, так как проблему можно решить **другим путём**. Тогда Вы можете предложить или спросить об этом.
|
||||
|
||||
### Не переживайте о стиле
|
||||
### Не переживайте о стиле { #dont-worry-about-style }
|
||||
|
||||
* Не стоит слишком беспокоиться о таких вещах, как стиль сообщений в коммитах или количество коммитов. При слиянии пул-реквеста с основной веткой, я буду сжимать и настраивать всё вручную.
|
||||
* Не стоит слишком беспокоиться о таких вещах, как стиль сообщений в коммитах — при слиянии я выполню squash и настрою коммит вручную.
|
||||
|
||||
* Также не беспокойтесь о правилах стиля, для проверки сего есть автоматизированные инструменты.
|
||||
* Также не беспокойтесь о правилах стиля, это уже проверяют автоматизированные инструменты.
|
||||
|
||||
И если всё же потребуется какой-то другой стиль, я попрошу Вас об этом напрямую или добавлю сам коммиты с необходимыми изменениями.
|
||||
Если будет нужна какая-то другая стилистика или единообразие, я попрошу об этом напрямую или добавлю поверх свои коммиты с нужными изменениями.
|
||||
|
||||
### Проверить код
|
||||
### Проверить код { #check-the-code }
|
||||
|
||||
* Проверьте и прочитайте код, посмотрите, какой он имеет смысл, **запустите его локально** и посмотрите, действительно ли он решает поставленную задачу.
|
||||
* Проверьте и прочитайте код, посмотрите, логичен ли он, **запустите его локально** и проверьте, действительно ли он решает проблему.
|
||||
|
||||
* Затем, используя **комментарий**, сообщите, что Вы сделали проверку, тогда я буду знать, что Вы действительно проверили код.
|
||||
* Затем оставьте **комментарий**, что Вы это сделали, так я пойму, что Вы действительно проверили код.
|
||||
|
||||
/// info | Информация
|
||||
|
||||
К сожалению, я не могу так просто доверять пул-реквестам, у которых уже есть несколько одобрений.
|
||||
К сожалению, я не могу просто доверять PR-ам только потому, что у них есть несколько одобрений.
|
||||
|
||||
Бывали случаи, что пул-реквесты имели 3, 5 или больше одобрений, вероятно из-за привлекательного описания, но когда я проверял эти пул-реквесты, они оказывались сломаны, содержали ошибки или вовсе не решали проблему, которую, как они утверждали, должны были решить. 😅
|
||||
Несколько раз было так, что у PR-ов было 3, 5 или больше одобрений, вероятно из-за привлекательного описания, но когда я их проверял, они оказывались сломанными, содержали баги или вовсе не решали заявленную проблему. 😅
|
||||
|
||||
Потому это действительно важно - проверять и запускать код, и комментарием уведомлять меня, что Вы проделали эти действия. 🤓
|
||||
Поэтому очень важно действительно прочитать и запустить код и сообщить мне об этом в комментарии. 🤓
|
||||
|
||||
///
|
||||
|
||||
* Если Вы считаете, что пул-реквест можно упростить, то можете попросить об этом, но не нужно быть слишком придирчивым, может быть много субъективных точек зрения (и у меня тоже будет своя 🙈), поэтому будет лучше, если Вы сосредоточитесь на фундаментальных вещах.
|
||||
* Если PR можно упростить, Вы можете попросить об этом, но не нужно быть слишком придирчивым — может быть много субъективных мнений (и у меня тоже 🙈), поэтому лучше сосредоточиться на фундаментальных вещах.
|
||||
|
||||
### Тестировать
|
||||
### Тестировать { #tests }
|
||||
|
||||
* Помогите мне проверить, что у пул-реквеста есть **тесты**.
|
||||
* Помогите мне проверить, что у PR есть **тесты**.
|
||||
|
||||
* Проверьте, что тесты **падали** до пул-реквеста. 🚨
|
||||
* Проверьте, что тесты **падают** до PR. 🚨
|
||||
|
||||
* Затем проверьте, что тесты **не валятся** после пул-реквеста. ✅
|
||||
* Затем проверьте, что тесты **проходят** после PR. ✅
|
||||
|
||||
* Многие пул-реквесты не имеют тестов, Вы можете **напомнить** о необходимости добавления тестов или даже **предложить** какие-либо свои тесты. Это одна из тех вещей, которые отнимают много времени и Вы можете помочь с этим.
|
||||
* Многие PR не имеют тестов — Вы можете **напомнить** добавить тесты или даже **предложить** некоторые тесты сами. Это одна из самых трудозатратных частей, и здесь Вы можете очень помочь.
|
||||
|
||||
* Затем добавьте комментарий, что Вы испробовали в ходе проверки. Таким образом я буду знать, как Вы произвели проверку. 🤓
|
||||
* Затем добавьте комментарий, что Вы попробовали, чтобы я знал, что Вы это проверили. 🤓
|
||||
|
||||
## Создать пул-реквест
|
||||
## Создать пулл-реквест { #create-a-pull-request }
|
||||
|
||||
Вы можете [сделать вклад](contributing.md){.internal-link target=_blank} в код фреймворка используя пул-реквесты, например:
|
||||
Вы можете [сделать вклад](contributing.md){.internal-link target=_blank} в исходный код пулл-реквестами, например:
|
||||
|
||||
* Исправить опечатку, которую Вы нашли в документации.
|
||||
* Поделиться статьёй, видео или подкастом о FastAPI, которые Вы создали или нашли <a href="https://github.com/fastapi/fastapi/edit/master/docs/en/data/external_links.yml" class="external-link" target="_blank">изменив этот файл</a>.
|
||||
* Убедитесь, что Вы добавили свою ссылку в начало соответствующего раздела.
|
||||
* Помочь с [переводом документации](contributing.md#_8){.internal-link target=_blank} на Ваш язык.
|
||||
* Вы также можете проверять переводы сделанные другими.
|
||||
* Исправить опечатку, найденную в документации.
|
||||
* Поделиться статьёй, видео или подкастом о FastAPI, которые Вы создали или нашли, <a href="https://github.com/fastapi/fastapi/edit/master/docs/en/data/external_links.yml" class="external-link" target="_blank">изменив этот файл</a>.
|
||||
* Убедитесь, что добавили свою ссылку в начало соответствующего раздела.
|
||||
* Помочь с [переводом документации](contributing.md#translations){.internal-link target=_blank} на Ваш язык.
|
||||
* Вы также можете проверять переводы, сделанные другими.
|
||||
* Предложить новые разделы документации.
|
||||
* Исправить существующуе проблемы/баги.
|
||||
* Исправить существующую проблему/баг.
|
||||
* Убедитесь, что добавили тесты.
|
||||
* Добавить новую возможность.
|
||||
* Убедитесь, что добавили тесты.
|
||||
* Убедитесь, что добавили документацию, если она необходима.
|
||||
* Убедитесь, что добавили документацию, если это уместно.
|
||||
|
||||
## Помочь поддерживать FastAPI
|
||||
## Помочь поддерживать FastAPI { #help-maintain-fastapi }
|
||||
|
||||
Помогите мне поддерживать **FastAPI**! 🤓
|
||||
|
||||
Предстоит ещё много работы и, по большей части, **ВЫ** можете её сделать.
|
||||
Предстоит ещё много работы, и, по большей части, **ВЫ** можете её сделать.
|
||||
|
||||
Основные задачи, которые Вы можете выполнить прямо сейчас:
|
||||
|
||||
* [Помочь другим с их проблемами на GitHub](#github_1){.internal-link target=_blank} (смотрите вышестоящую секцию).
|
||||
* [Проверить пул-реквесты](#-){.internal-link target=_blank} (смотрите вышестоящую секцию).
|
||||
* [Помочь другим с вопросами на GitHub](#help-others-with-questions-in-github){.internal-link target=_blank} (смотрите секцию выше).
|
||||
* [Проверять пулл-реквесты](#review-pull-requests){.internal-link target=_blank} (смотрите секцию выше).
|
||||
|
||||
Эти две задачи **отнимают больше всего времени**. Это основная работа по поддержке FastAPI.
|
||||
Именно эти две задачи **забирают больше всего времени**. Это основная работа по поддержке FastAPI.
|
||||
|
||||
Если Вы можете помочь мне с этим, **Вы помогаете поддерживать FastAPI** и следить за тем, чтобы он продолжал **развиваться быстрее и лучше**. 🚀
|
||||
Если Вы можете помочь мне с этим, **Вы помогаете поддерживать FastAPI** и делаете так, чтобы он продолжал **развиваться быстрее и лучше**. 🚀
|
||||
|
||||
## Подключиться к чату
|
||||
## Подключиться к чату { #join-the-chat }
|
||||
|
||||
Подключайтесь к 👥 <a href="https://discord.gg/VQjSZaeJmf" class="external-link" target="_blank"> чату в Discord</a> 👥 и общайтесь с другими участниками сообщества FastAPI.
|
||||
Подключайтесь к 👥 <a href="https://discord.gg/VQjSZaeJmf" class="external-link" target="_blank">серверу чата в Discord</a> 👥 и общайтесь с другими участниками сообщества FastAPI.
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Вопросы по проблемам с фреймворком лучше задавать в <a href="https://github.com/fastapi/fastapi/issues/new/choose" class="external-link" target="_blank">GitHub issues</a>, так больше шансов, что Вы получите помощь от [Экспертов FastAPI](fastapi-people.md#_3){.internal-link target=_blank}.
|
||||
По вопросам — задавайте их в <a href="https://github.com/fastapi/fastapi/discussions/new?category=questions" class="external-link" target="_blank">GitHub Discussions</a>, так гораздо выше шанс, что Вы получите помощь от [Экспертов FastAPI](fastapi-people.md#fastapi-experts){.internal-link target=_blank}.
|
||||
|
||||
Используйте этот чат только для бесед на отвлечённые темы.
|
||||
Используйте чат только для прочих общих бесед.
|
||||
|
||||
///
|
||||
|
||||
### Не использовать чаты для вопросов
|
||||
### Не используйте чат для вопросов { #dont-use-the-chat-for-questions }
|
||||
|
||||
Имейте в виду, что чаты позволяют больше "свободного общения", потому там легко задавать вопросы, которые слишком общие и на которые труднее ответить, так что Вы можете не получить нужные Вам ответы.
|
||||
Имейте в виду, что в чатах, благодаря "свободному общению", легко задать вопросы, которые слишком общие и на которые сложнее ответить, поэтому Вы можете не получить ответы.
|
||||
|
||||
В разделе "проблемы" на GitHub, есть шаблон, который поможет Вам написать вопрос правильно, чтобы Вам было легче получить хороший ответ или даже решить проблему самостоятельно, прежде чем Вы зададите вопрос. В GitHub я могу быть уверен, что всегда отвечаю на всё, даже если это займет какое-то время. И я не могу сделать то же самое в чатах. 😅
|
||||
На GitHub шаблон поможет Вам правильно сформулировать вопрос, чтобы Вам было легче получить хороший ответ или даже решить проблему самостоятельно ещё до того, как спросите. И на GitHub я могу следить за тем, чтобы всегда отвечать на всё, даже если это занимает время. А с чатами я не могу сделать этого лично. 😅
|
||||
|
||||
Кроме того, общение в чатах не так легкодоступно для поиска, как в GitHub, потому вопросы и ответы могут потеряться среди другого общения. И только проблемы решаемые на GitHub учитываются в получении лычки [Эксперт FastAPI](fastapi-people.md#_3){.internal-link target=_blank}, так что весьма вероятно, что Вы получите больше внимания на GitHub.
|
||||
Кроме того, переписка в чатах хуже ищется, чем на GitHub, поэтому вопросы и ответы могут теряться среди остальных сообщений. И только те, что на GitHub, учитываются для получения лычки [Эксперт FastAPI](fastapi-people.md#fastapi-experts){.internal-link target=_blank}, так что вероятнее всего Вы получите больше внимания именно на GitHub.
|
||||
|
||||
С другой стороны, в чатах тысячи пользователей, а значит есть большие шансы в любое время найти там кого-то, с кем можно поговорить. 😄
|
||||
С другой стороны, в чатах тысячи пользователей, так что почти всегда есть шанс найти там кого-то для разговора. 😄
|
||||
|
||||
## Спонсировать автора
|
||||
## Спонсировать автора { #sponsor-the-author }
|
||||
|
||||
Вы также можете оказать мне финансовую поддержку посредством <a href="https://github.com/sponsors/tiangolo" class="external-link" target="_blank">спонсорства через GitHub</a>.
|
||||
|
||||
Там можно просто купить мне кофе ☕️ в знак благодарности. 😄
|
||||
|
||||
А ещё Вы можете стать Серебряным или Золотым спонсором для FastAPI. 🏅🎉
|
||||
|
||||
## Спонсировать инструменты, на которых зиждется мощь FastAPI
|
||||
|
||||
Как Вы могли заметить в документации, FastAPI опирается на плечи титанов: Starlette и Pydantic.
|
||||
|
||||
Им тоже можно оказать спонсорскую поддержку:
|
||||
|
||||
* <a href="https://github.com/sponsors/samuelcolvin" class="external-link" target="_blank">Samuel Colvin (Pydantic)</a>
|
||||
* <a href="https://github.com/sponsors/encode" class="external-link" target="_blank">Encode (Starlette, Uvicorn)</a>
|
||||
Если Ваш **продукт/компания** зависят от **FastAPI** или связаны с ним и Вы хотите донести до пользователей информацию о себе, Вы можете спонсировать автора (меня) через <a href="https://github.com/sponsors/tiangolo" class="external-link" target="_blank">GitHub Sponsors</a>. В зависимости от уровня поддержки Вы можете получить дополнительные бонусы, например, бейдж в документации. 🎁
|
||||
|
||||
---
|
||||
|
||||
Благодарствую! 🚀
|
||||
Спасибо! 🚀
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# История создания и дальнейшее развитие
|
||||
# История, проектирование и будущее { #history-design-and-future }
|
||||
|
||||
Однажды, <a href="https://github.com/fastapi/fastapi/issues/3#issuecomment-454956920" class="external-link" target="_blank">один из пользователей **FastAPI** задал вопрос</a>:
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
Что ж, вот небольшая часть истории проекта.
|
||||
|
||||
## Альтернативы
|
||||
## Альтернативы { #alternatives }
|
||||
|
||||
В течение нескольких лет я, возглавляя различные команды разработчиков, создавал довольно сложные API для машинного обучения, распределённых систем, асинхронных задач, баз данных NoSQL и т.д.
|
||||
|
||||
@@ -24,45 +24,47 @@
|
||||
|
||||
Я всячески избегал создания нового фреймворка в течение нескольких лет. Сначала я пытался собрать все нужные возможности, которые ныне есть в **FastAPI**, используя множество различных фреймворков, плагинов и инструментов.
|
||||
|
||||
Но в какой-то момент не осталось другого выбора, кроме как создать что-то, что предоставляло бы все эти возможности сразу. Взять самые лучшие идеи из предыдущих инструментов и, используя введённые в Python подсказки типов (которых не было до версии 3.6), объединить их.
|
||||
Но в какой-то момент не осталось другого выбора, кроме как создать что-то, что предоставляло бы все эти возможности сразу. Взять самые лучшие идеи из предыдущих инструментов и, используя введённые в Python аннотации типов (которых не было до версии 3.6), объединить их.
|
||||
|
||||
</blockquote>
|
||||
|
||||
## Исследования
|
||||
## Исследования { #investigation }
|
||||
|
||||
Благодаря опыту использования существующих альтернатив, мы с коллегами изучили их основные идеи и скомбинировали собранные знания наилучшим образом.
|
||||
|
||||
Например, стало ясно, что необходимо брать за основу стандартные подсказки типов Python, а самым лучшим подходом является использование уже существующих стандартов.
|
||||
Например, стало ясно, что необходимо брать за основу стандартные аннотации типов Python.
|
||||
|
||||
Также наилучшим подходом является использование уже существующих стандартов.
|
||||
|
||||
Итак, прежде чем приступить к написанию **FastAPI**, я потратил несколько месяцев на изучение OpenAPI, JSON Schema, OAuth2, и т.п. для понимания их взаимосвязей, совпадений и различий.
|
||||
|
||||
## Дизайн
|
||||
## Проектирование { #design }
|
||||
|
||||
Затем я потратил некоторое время на придумывание "API" разработчика, который я хотел иметь как пользователь (как разработчик, использующий FastAPI).
|
||||
|
||||
Я проверил несколько идей на самых популярных редакторах кода среди Python-разработчиков: PyCharm, VS Code, Jedi.
|
||||
Я проверил несколько идей на самых популярных редакторах кода: PyCharm, VS Code, редакторы на базе Jedi.
|
||||
|
||||
Данные по редакторам я взял из <a href="https://www.jetbrains.com/research/python-developers-survey-2018/#development-tools" class="external-link" target="_blank">опроса Python-разработчиков</a>, который охватываает около 80% пользователей.
|
||||
Данные по редакторам я взял из <a href="https://www.jetbrains.com/research/python-developers-survey-2018/#development-tools" class="external-link" target="_blank">опроса Python-разработчиков</a>, который охватывает около 80% пользователей.
|
||||
|
||||
Это означает, что **FastAPI** был специально проверен на редакторах, используемых 80% Python-разработчиками. И поскольку большинство других редакторов, как правило, работают аналогичным образом, все его преимущества должны работать практически для всех редакторов.
|
||||
|
||||
Таким образом, я смог найти наилучшие способы сократить дублирование кода, обеспечить повсеместное автодополнение, проверку типов и ошибок и т.д.
|
||||
Таким образом, я смог найти наилучшие способы сократить дублирование кода, обеспечить повсеместное автозавершение, проверку типов и ошибок и т.д.
|
||||
|
||||
И все это, чтобы все пользователи могли получать наилучший опыт разработки.
|
||||
|
||||
## Зависимости
|
||||
## Зависимости { #requirements }
|
||||
|
||||
Протестировав несколько вариантов, я решил, что в качестве основы буду использовать <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">**Pydantic**</a> и его преимущества.
|
||||
|
||||
По моим предложениям был изменён код этого фреймворка, чтобы сделать его полностью совместимым с JSON Schema, поддержать различные способы определения ограничений и улучшить помощь редакторов (проверки типов, автозаполнение).
|
||||
По моим предложениям был изменён код этого фреймворка, чтобы сделать его полностью совместимым с JSON Schema, поддержать различные способы определения ограничений и улучшить поддержку в редакторах кода (проверки типов, автозавершение) на основе тестов в нескольких редакторах.
|
||||
|
||||
В то же время, я принимал участие в разработке <a href="https://www.starlette.io/" class="external-link" target="_blank">**Starlette**</a>, ещё один из основных компонентов FastAPI.
|
||||
|
||||
## Разработка
|
||||
## Разработка { #development }
|
||||
|
||||
К тому времени, когда я начал создавать **FastAPI**, большинство необходимых деталей уже существовало, дизайн был определён, зависимости и прочие инструменты были готовы, а знания о стандартах и спецификациях были четкими и свежими.
|
||||
|
||||
## Будущее
|
||||
## Будущее { #future }
|
||||
|
||||
Сейчас уже ясно, что **FastAPI** со своими идеями стал полезен многим людям.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# FastAPI
|
||||
# FastAPI { #fastapi }
|
||||
|
||||
<style>
|
||||
.md-content .md-typeset h1 { display: none; }
|
||||
@@ -8,47 +8,47 @@
|
||||
<a href="https://fastapi.tiangolo.com"><img src="https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" alt="FastAPI"></a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<em>Готовый к внедрению высокопроизводительный фреймворк, простой в изучении и разработке.</em>
|
||||
<em>Фреймворк FastAPI: высокая производительность, прост в изучении, быстрый в разработке, готов к продакшн</em>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://github.com/fastapi/fastapi/actions?query=workflow%3ATest+event%3Apush+branch%3Amaster" target="_blank">
|
||||
<img src="https://github.com/fastapi/fastapi/actions/workflows/test.yml/badge.svg?event=push&branch=master" alt="Test">
|
||||
<img src="https://github.com/fastapi/fastapi/actions/workflows/test.yml/badge.svg?event=push&branch=master" alt="Тест">
|
||||
</a>
|
||||
<a href="https://coverage-badge.samuelcolvin.workers.dev/redirect/fastapi/fastapi" target="_blank">
|
||||
<img src="https://coverage-badge.samuelcolvin.workers.dev/fastapi/fastapi.svg" alt="Coverage">
|
||||
<img src="https://coverage-badge.samuelcolvin.workers.dev/fastapi/fastapi.svg" alt="Покрытие">
|
||||
</a>
|
||||
<a href="https://pypi.org/project/fastapi" target="_blank">
|
||||
<img src="https://img.shields.io/pypi/v/fastapi?color=%2334D058&label=pypi%20package" alt="Package version">
|
||||
<img src="https://img.shields.io/pypi/v/fastapi?color=%2334D058&label=pypi%20package" alt="Версия пакета">
|
||||
</a>
|
||||
<a href="https://pypi.org/project/fastapi" target="_blank">
|
||||
<img src="https://img.shields.io/pypi/pyversions/fastapi.svg?color=%2334D058" alt="Supported Python versions">
|
||||
<img src="https://img.shields.io/pypi/pyversions/fastapi.svg?color=%2334D058" alt="Поддерживаемые версии Python">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
**Документация**: <a href="https://fastapi.tiangolo.com" target="_blank">https://fastapi.tiangolo.com</a>
|
||||
**Документация**: <a href="https://fastapi.tiangolo.com/ru" target="_blank">https://fastapi.tiangolo.com</a>
|
||||
|
||||
**Исходный код**: <a href="https://github.com/fastapi/fastapi" target="_blank">https://github.com/fastapi/fastapi</a>
|
||||
|
||||
---
|
||||
|
||||
FastAPI — это современный, быстрый (высокопроизводительный) веб-фреймворк для создания API используя Python, в основе которого лежит стандартная аннотация типов Python.
|
||||
FastAPI — это современный, быстрый (высокопроизводительный) веб-фреймворк для создания API на Python, основанный на стандартных аннотациях типов Python.
|
||||
|
||||
Ключевые особенности:
|
||||
|
||||
* **Скорость**: Очень высокая производительность, на уровне **NodeJS** и **Go** (благодаря Starlette и Pydantic). [Один из самых быстрых фреймворков Python](#_10).
|
||||
* **Быстрота разработки**: Увеличьте скорость разработки примерно на 200–300%. *
|
||||
* **Скорость**: Очень высокая производительность, на уровне **NodeJS** и **Go** (благодаря Starlette и Pydantic). [Один из самых быстрых доступных фреймворков Python](#performance).
|
||||
* **Быстрота разработки**: Увеличьте скорость разработки фич примерно на 200–300%. *
|
||||
* **Меньше ошибок**: Сократите примерно на 40% количество ошибок, вызванных человеком (разработчиком). *
|
||||
* **Интуитивно понятный**: Отличная поддержка редактора. <abbr title="также известное как автозаполнение, автодополнение, IntelliSense">Автозавершение</abbr> везде. Меньше времени на отладку.
|
||||
* **Лёгкость**: Разработан так, чтобы его было легко использовать и осваивать. Меньше времени на чтение документации.
|
||||
* **Краткость**: Сведите к минимуму дублирование кода. Каждый объявленный параметр - определяет несколько функций. Меньше ошибок.
|
||||
* **Надежность**: Получите готовый к работе код. С автоматической интерактивной документацией.
|
||||
* **На основе стандартов**: Основан на открытых стандартах API и полностью совместим с ними: <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> (ранее известном как Swagger) и <a href="https://json-schema.org/" class="external-link" target="_blank">JSON Schema</a>.
|
||||
* **Интуитивность**: Отличная поддержка редактора кода. <abbr title="также известное как: автодополнение, IntelliSense">Автозавершение</abbr> везде. Меньше времени на отладку.
|
||||
* **Простота**: Разработан так, чтобы его было легко использовать и осваивать. Меньше времени на чтение документации.
|
||||
* **Краткость**: Минимизируйте дублирование кода. Несколько возможностей из каждого объявления параметров. Меньше ошибок.
|
||||
* **Надежность**: Получите код, готовый к продакшн. С автоматической интерактивной документацией.
|
||||
* **На основе стандартов**: Основан на открытых стандартах API и полностью совместим с ними: <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> (ранее известный как Swagger) и <a href="https://json-schema.org/" class="external-link" target="_blank">JSON Schema</a>.
|
||||
|
||||
<small>* оценка на основе тестов внутренней команды разработчиков, создающих производственные приложения.</small>
|
||||
<small>* оценка на основе тестов внутренней команды разработчиков, создающих продакшн-приложения.</small>
|
||||
|
||||
## Спонсоры
|
||||
## Спонсоры { #sponsors }
|
||||
|
||||
<!-- sponsors -->
|
||||
|
||||
@@ -63,23 +63,23 @@ FastAPI — это современный, быстрый (высокопрои
|
||||
|
||||
<!-- /sponsors -->
|
||||
|
||||
<a href="https://fastapi.tiangolo.com/fastapi-people/#sponsors" class="external-link" target="_blank">Другие спонсоры</a>
|
||||
<a href="https://fastapi.tiangolo.com/ru/fastapi-people/#sponsors" class="external-link" target="_blank">Другие спонсоры</a>
|
||||
|
||||
## Отзывы
|
||||
## Мнения { #opinions }
|
||||
|
||||
"_В последнее время я много где использую **FastAPI**. [...] На самом деле я планирую использовать его для всех **сервисов машинного обучения моей команды в Microsoft**. Некоторые из них интегрируются в основной продукт **Windows**, а некоторые — в продукты **Office**._"
|
||||
"_[...] В последнее время я много где использую **FastAPI**. [...] На самом деле я планирую использовать его для всех **ML-сервисов моей команды в Microsoft**. Некоторые из них интегрируются в основной продукт **Windows**, а некоторые — в продукты **Office**._"
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Kabir Khan - <strong>Microsoft</strong> <a href="https://github.com/fastapi/fastapi/pull/26" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
"_Мы использовали библиотеку **FastAPI** для создания сервера **REST**, к которому можно делать запросы для получения **прогнозов**. [для Ludwig]_"
|
||||
"_Мы начали использовать библиотеку **FastAPI**, чтобы поднять **REST**-сервер, к которому можно обращаться за **предсказаниями**. [для Ludwig]_"
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - <strong>Uber</strong> <a href="https://eng.uber.com/ludwig-v0-2/" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
"_**Netflix** рада объявить о выпуске опенсорсного фреймворка для оркестровки **антикризисного управления**: **Dispatch**! [создана с помощью **FastAPI**]_"
|
||||
"_**Netflix** рада объявить об открытом релизе нашего фреймворка оркестрации **антикризисного управления**: **Dispatch**! [создан с помощью **FastAPI**]_"
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Kevin Glisson, Marc Vilanova, Forest Monsen - <strong>Netflix</strong> <a href="https://netflixtechblog.com/introducing-dispatch-da4b8a2a8072" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
@@ -87,68 +87,66 @@ FastAPI — это современный, быстрый (высокопрои
|
||||
|
||||
"_Я в полном восторге от **FastAPI**. Это так весело!_"
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Brian Okken - <strong><a href="https://pythonbytes.fm/episodes/show/123/time-to-right-the-py-wrongs?time_in_sec=855" target="_blank">Python Bytes</a> podcast host</strong> <a href="https://x.com/brianokken/status/1112220079972728832" target="_blank"><small>(ref)</small></a></div>
|
||||
<div style="text-align: right; margin-right: 10%;">Brian Okken - <strong>Ведущий подкаста <a href="https://pythonbytes.fm/episodes/show/123/time-to-right-the-py-wrongs?time_in_sec=855" target="_blank">Python Bytes</a></strong> <a href="https://x.com/brianokken/status/1112220079972728832" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
"_Честно говоря, то, что вы создали, выглядит очень солидно и отполировано. Во многих смыслах я хотел, чтобы **Hug** был именно таким — это действительно вдохновляет, когда кто-то создаёт такое._"
|
||||
"_Честно говоря, то, что вы создали, выглядит очень солидно и отполировано. Во многих смыслах это то, чем я хотел видеть **Hug** — очень вдохновляет видеть, как кто-то это создал._"
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Timothy Crosley - <strong><a href="https://github.com/hugapi/hug" target="_blank">Hug</a> creator</strong> <a href="https://news.ycombinator.com/item?id=19455465" target="_blank"><small>(ref)</small></a></div>
|
||||
<div style="text-align: right; margin-right: 10%;">Timothy Crosley - <strong>Создатель <a href="https://github.com/hugapi/hug" target="_blank">Hug</a></strong> <a href="https://news.ycombinator.com/item?id=19455465" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
"_Если вы хотите изучить какой-нибудь **современный фреймворк** для создания REST API, ознакомьтесь с **FastAPI** [...] Он быстрый, лёгкий и простой в изучении [...]_"
|
||||
"_Если вы хотите изучить один **современный фреймворк** для создания REST API, посмотрите **FastAPI** [...] Он быстрый, простой в использовании и лёгкий в изучении [...]_"
|
||||
|
||||
"_Мы перешли на **FastAPI** для наших **API** [...] Я думаю, вам тоже понравится [...]_"
|
||||
"_Мы переключились на **FastAPI** для наших **API** [...] Думаю, вам тоже понравится [...]_"
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Ines Montani - Matthew Honnibal - <strong><a href="https://explosion.ai" target="_blank">Explosion AI</a> founders - <a href="https://spacy.io" target="_blank">spaCy</a> creators</strong> <a href="https://x.com/_inesmontani/status/1144173225322143744" target="_blank"><small>(ref)</small></a> - <a href="https://x.com/honnibal/status/1144031421859655680" target="_blank"><small>(ref)</small></a></div>
|
||||
<div style="text-align: right; margin-right: 10%;">Ines Montani - Matthew Honnibal - <strong>Основатели <a href="https://explosion.ai" target="_blank">Explosion AI</a> — создатели <a href="https://spacy.io" target="_blank">spaCy</a></strong> <a href="https://x.com/_inesmontani/status/1144173225322143744" target="_blank"><small>(ref)</small></a> - <a href="https://x.com/honnibal/status/1144031421859655680" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
## **Typer**, интерфейс командной строки для FastAPI
|
||||
"_Если кто-то собирается делать продакшн-API на Python, я настоятельно рекомендую **FastAPI**. Он **прекрасно спроектирован**, **прост в использовании** и **отлично масштабируется**, стал **ключевым компонентом** нашей стратегии API-first и приводит в действие множество автоматизаций и сервисов, таких как наш Virtual TAC Engineer._"
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Deon Pillsbury - <strong>Cisco</strong> <a href="https://www.linkedin.com/posts/deonpillsbury_cisco-cx-python-activity-6963242628536487936-trAp/" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
## **Typer**, FastAPI для CLI { #typer-the-fastapi-of-clis }
|
||||
|
||||
<a href="https://typer.tiangolo.com" target="_blank"><img src="https://typer.tiangolo.com/img/logo-margin/logo-margin-vector.svg" style="width: 20%;"></a>
|
||||
|
||||
Если вы создаете приложение <abbr title="Интерфейс командной строки">CLI</abbr> для использования в терминале вместо веб-API, ознакомьтесь с <a href="https://typer.tiangolo.com/" class="external-link" target="_blank">**Typer**</a>.
|
||||
Если вы создаёте приложение <abbr title="Command Line Interface – Интерфейс командной строки">CLI</abbr> для использования в терминале вместо веб-API, посмотрите <a href="https://typer.tiangolo.com/" class="external-link" target="_blank">**Typer**</a>.
|
||||
|
||||
**Typer** — младший брат FastAPI. И он предназначен для использования в качестве **интерфейса командной строки для FastAPI**. ⌨️ 🚀
|
||||
**Typer** — младший брат FastAPI. И он задуман как **FastAPI для CLI**. ⌨️ 🚀
|
||||
|
||||
## Зависимости
|
||||
## Зависимости { #requirements }
|
||||
|
||||
FastAPI стоит на плечах гигантов:
|
||||
|
||||
* <a href="https://www.starlette.io/" class="external-link" target="_blank">Starlette</a> для части связанной с вебом.
|
||||
* <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a> для части связанной с данными.
|
||||
* <a href="https://www.starlette.io/" class="external-link" target="_blank">Starlette</a> для части, связанной с вебом.
|
||||
* <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a> для части, связанной с данными.
|
||||
|
||||
## Установка
|
||||
## Установка { #installation }
|
||||
|
||||
Создайте и активируйте <a href="https://fastapi.tiangolo.com/ru/virtual-environments/" class="external-link" target="_blank">виртуальное окружение</a>, затем установите FastAPI:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install fastapi
|
||||
$ pip install "fastapi[standard]"
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
Вам также понадобится сервер ASGI для производства, такой как <a href="https://www.uvicorn.org" class="external-link" target="_blank">Uvicorn</a> или <a href="https://github.com/pgjones/hypercorn" class="external-link" target="_blank">Hypercorn</a>.
|
||||
**Примечание**: Обязательно заключите `"fastapi[standard]"` в кавычки, чтобы это работало во всех терминалах.
|
||||
|
||||
<div class="termy">
|
||||
## Пример { #example }
|
||||
|
||||
```console
|
||||
$ pip install "uvicorn[standard]"
|
||||
### Создание { #create-it }
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## Пример
|
||||
|
||||
### Создание
|
||||
|
||||
* Создайте файл `main.py` со следующим содержимым:
|
||||
Создайте файл `main.py` со следующим содержимым:
|
||||
|
||||
```Python
|
||||
from typing import Union
|
||||
@@ -193,22 +191,35 @@ async def read_item(item_id: int, q: Union[str, None] = None):
|
||||
|
||||
**Примечание**:
|
||||
|
||||
Если вы не знаете, проверьте раздел _"Торопитесь?"_ <a href="https://fastapi.tiangolo.com/async/#in-a-hurry" target="_blank">в документации об `async` и `await`</a>.
|
||||
Если не уверены, посмотрите раздел _«Нет времени?»_ о <a href="https://fastapi.tiangolo.com/ru/async/#in-a-hurry" target="_blank">`async` и `await` в документации</a>.
|
||||
|
||||
</details>
|
||||
|
||||
### Запуск
|
||||
### Запуск { #run-it }
|
||||
|
||||
Запустите сервер с помощью:
|
||||
Запустите сервер командой:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ uvicorn main:app --reload
|
||||
$ fastapi dev main.py
|
||||
|
||||
╭────────── FastAPI CLI - Development mode ───────────╮
|
||||
│ │
|
||||
│ Serving at: http://127.0.0.1:8000 │
|
||||
│ │
|
||||
│ API docs: http://127.0.0.1:8000/docs │
|
||||
│ │
|
||||
│ Running in development mode, for production use: │
|
||||
│ │
|
||||
│ fastapi run │
|
||||
│ │
|
||||
╰─────────────────────────────────────────────────────╯
|
||||
|
||||
INFO: Will watch for changes in these directories: ['/home/user/code/awesomeapp']
|
||||
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
INFO: Started reloader process [28720]
|
||||
INFO: Started server process [28722]
|
||||
INFO: Started reloader process [2248755] using WatchFiles
|
||||
INFO: Started server process [2248757]
|
||||
INFO: Waiting for application startup.
|
||||
INFO: Application startup complete.
|
||||
```
|
||||
@@ -216,21 +227,21 @@ INFO: Application startup complete.
|
||||
</div>
|
||||
|
||||
<details markdown="1">
|
||||
<summary>О команде <code>uvicorn main:app --reload</code>...</summary>
|
||||
<summary>О команде <code>fastapi dev main.py</code>...</summary>
|
||||
|
||||
Команда `uvicorn main:app` относится к:
|
||||
Команда `fastapi dev` читает ваш файл `main.py`, находит в нём приложение **FastAPI** и запускает сервер с помощью <a href="https://www.uvicorn.org" class="external-link" target="_blank">Uvicorn</a>.
|
||||
|
||||
* `main`: файл `main.py` (модуль Python).
|
||||
* `app`: объект, созданный внутри `main.py` с помощью строки `app = FastAPI()`.
|
||||
* `--reload`: перезапуск сервера после изменения кода. Делайте это только во время разработки.
|
||||
По умолчанию `fastapi dev` запускается с включённой авто-перезагрузкой для локальной разработки.
|
||||
|
||||
Подробнее в <a href="https://fastapi.tiangolo.com/ru/fastapi-cli/" target="_blank">документации по FastAPI CLI</a>.
|
||||
|
||||
</details>
|
||||
|
||||
### Проверка
|
||||
### Проверка { #check-it }
|
||||
|
||||
Откройте браузер на <a href="http://127.0.0.1:8000/items/5?q=somequery" class="external-link" target="_blank">http://127.0.0.1:8000/items/5?q=somequery</a>.
|
||||
|
||||
Вы увидите следующий JSON ответ:
|
||||
Вы увидите JSON-ответ:
|
||||
|
||||
```JSON
|
||||
{"item_id": 5, "q": "somequery"}
|
||||
@@ -239,31 +250,31 @@ INFO: Application startup complete.
|
||||
Вы уже создали API, который:
|
||||
|
||||
* Получает HTTP-запросы по _путям_ `/` и `/items/{item_id}`.
|
||||
* И первый и второй _путь_ используют `GET` <em>операции</em> (также известные как HTTP _методы_).
|
||||
* _путь_ `/items/{item_id}` имеет _параметр пути_ `item_id`, который должен быть `int`.
|
||||
* _путь_ `/items/{item_id}` имеет необязательный `str` _параметр запроса_ `q`.
|
||||
* Оба _пути_ используют `GET` <em>операции</em> (также известные как HTTP _методы_).
|
||||
* _Путь_ `/items/{item_id}` имеет _параметр пути_ `item_id`, который должен быть `int`.
|
||||
* _Путь_ `/items/{item_id}` имеет необязательный `str` _параметр запроса_ `q`.
|
||||
|
||||
### Интерактивная документация по API
|
||||
### Интерактивная документация API { #interactive-api-docs }
|
||||
|
||||
Перейдите на <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
|
||||
|
||||
Вы увидите автоматическую интерактивную документацию API (предоставленную <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>):
|
||||
Вы увидите автоматическую интерактивную документацию API (предоставлена <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>):
|
||||
|
||||

|
||||
|
||||
### Альтернативная документация по API
|
||||
### Альтернативная документация API { #alternative-api-docs }
|
||||
|
||||
А теперь перейдите на <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>.
|
||||
Теперь откройте <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>.
|
||||
|
||||
Вы увидите альтернативную автоматическую документацию (предоставленную <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>):
|
||||
Вы увидите альтернативную автоматическую документацию (предоставлена <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>):
|
||||
|
||||

|
||||
|
||||
## Пример обновления
|
||||
## Пример обновления { #example-upgrade }
|
||||
|
||||
Теперь измените файл `main.py`, чтобы получить тело ответа из `PUT` запроса.
|
||||
Теперь измените файл `main.py`, чтобы принимать тело запроса из `PUT` запроса.
|
||||
|
||||
Объявите тело, используя стандартную типизацию Python, спасибо Pydantic.
|
||||
Объявите тело, используя стандартные типы Python, спасибо Pydantic.
|
||||
|
||||
```Python hl_lines="4 9-12 25-27"
|
||||
from typing import Union
|
||||
@@ -295,39 +306,39 @@ def update_item(item_id: int, item: Item):
|
||||
return {"item_name": item.name, "item_id": item_id}
|
||||
```
|
||||
|
||||
Сервер должен перезагрузиться автоматически (потому что вы добавили `--reload` к команде `uvicorn` выше).
|
||||
Сервер `fastapi dev` должен перезагрузиться автоматически.
|
||||
|
||||
### Интерактивное обновление документации API
|
||||
### Обновление интерактивной документации API { #interactive-api-docs-upgrade }
|
||||
|
||||
Перейдите на <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
|
||||
|
||||
* Интерактивная документация API будет автоматически обновляться, включая новое тело:
|
||||
* Интерактивная документация API будет автоматически обновлена, включая новое тело:
|
||||
|
||||

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

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

|
||||
|
||||
### Альтернативное обновление документации API
|
||||
### Обновление альтернативной документации API { #alternative-api-docs-upgrade }
|
||||
|
||||
А теперь перейдите на <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>.
|
||||
Теперь откройте <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>.
|
||||
|
||||
* Альтернативная документация также будет отражать новый параметр и тело запроса:
|
||||
* Альтернативная документация также отразит новый параметр запроса и тело:
|
||||
|
||||

|
||||
|
||||
### Подведём итоги
|
||||
### Подведём итоги { #recap }
|
||||
|
||||
Таким образом, вы объявляете **один раз** типы параметров, тело и т. д. в качестве параметров функции.
|
||||
Итак, вы объявляете **один раз** типы параметров, тела запроса и т.д. как параметры функции.
|
||||
|
||||
Вы делаете это используя стандартную современную типизацию Python.
|
||||
Вы делаете это с помощью стандартных современных типов Python.
|
||||
|
||||
Вам не нужно изучать новый синтаксис, методы или классы конкретной библиотеки и т. д.
|
||||
Вам не нужно изучать новый синтаксис, методы или классы конкретной библиотеки и т.п.
|
||||
|
||||
Только стандартный **Python**.
|
||||
|
||||
@@ -343,29 +354,29 @@ item_id: int
|
||||
item: Item
|
||||
```
|
||||
|
||||
... и с этим единственным объявлением вы получаете:
|
||||
...и с этим единственным объявлением вы получаете:
|
||||
|
||||
* Поддержка редактора, в том числе:
|
||||
* Поддержку редактора кода, включая:
|
||||
* Автозавершение.
|
||||
* Проверка типов.
|
||||
* Валидация данных:
|
||||
* Автоматические и четкие ошибки, когда данные недействительны.
|
||||
* Проверка даже для глубоко вложенных объектов JSON.
|
||||
* <abbr title="также известный как: сериализация, синтаксический анализ, маршалинг">Преобразование</abbr> входных данных: поступающие из сети в объекты Python с соблюдением типов. Чтение из:
|
||||
* Проверку типов.
|
||||
* Валидацию данных:
|
||||
* Автоматические и понятные ошибки, когда данные некорректны.
|
||||
* Валидацию даже для глубоко вложенных объектов JSON.
|
||||
* <abbr title="также известное как: сериализация, парсинг, маршалинг">Преобразование</abbr> входных данных: из сети в данные и типы Python. Чтение из:
|
||||
* JSON.
|
||||
* Параметров пути.
|
||||
* Параметров запроса.
|
||||
* Cookies.
|
||||
* Заголовков.
|
||||
* HTTP-заголовков.
|
||||
* Форм.
|
||||
* Файлов.
|
||||
* <abbr title="также известный как: сериализация, синтаксический анализ, маршалинг">Преобразование</abbr> выходных данных: преобразование объектов Python в данные передаваемые по сети интернет (такие как JSON):
|
||||
* Преобразование типов Python (`str`, `int`, `float`, `bool`, `list`, и т.д.).
|
||||
* <abbr title="также известное как: сериализация, парсинг, маршалинг">Преобразование</abbr> выходных данных: из данных и типов Python в данные сети (например, JSON):
|
||||
* Преобразование типов Python (`str`, `int`, `float`, `bool`, `list` и т.д.).
|
||||
* Объекты `datetime`.
|
||||
* Объекты `UUID`.
|
||||
* Модели баз данных.
|
||||
* ...и многое другое.
|
||||
* Автоматическая интерактивная документация по API, включая 2 альтернативных пользовательских интерфейса:
|
||||
* Автоматическую интерактивную документацию API, включая 2 альтернативных интерфейса:
|
||||
* Swagger UI.
|
||||
* ReDoc.
|
||||
|
||||
@@ -373,28 +384,28 @@ item: Item
|
||||
|
||||
Возвращаясь к предыдущему примеру кода, **FastAPI** будет:
|
||||
|
||||
* Проверять наличие `item_id` в пути для запросов `GET` и `PUT`.
|
||||
* Проверять, что `item_id` имеет тип `int` для запросов `GET` и `PUT`.
|
||||
* Если это не так, клиент увидит полезную чёткую ошибку.
|
||||
* Валидировать наличие `item_id` в пути для `GET` и `PUT` запросов.
|
||||
* Валидировать, что `item_id` имеет тип `int` для `GET` и `PUT` запросов.
|
||||
* Если это не так, клиент увидит полезную понятную ошибку.
|
||||
* Проверять, есть ли необязательный параметр запроса с именем `q` (например, `http://127.0.0.1:8000/items/foo?q=somequery`) для `GET` запросов.
|
||||
* Поскольку параметр `q` объявлен с `= None`, он является необязательным.
|
||||
* Без `None` он был бы необходим (как тело в случае с `PUT`).
|
||||
* Для `PUT` запросов к `/items/{item_id}` читать тело как JSON:
|
||||
* Проверять, что у него есть обязательный атрибут `name`, который должен быть `str`.
|
||||
* Проверять, что у него есть обязательный атрибут `price`, который должен быть `float`.
|
||||
* Проверять, что у него есть необязательный атрибут `is_offer`, который должен быть `bool`, если он присутствует.
|
||||
* Все это также будет работать для глубоко вложенных объектов JSON.
|
||||
* Преобразовывать из и в JSON автоматически.
|
||||
* Документировать с помощью OpenAPI все, что может быть использовано:
|
||||
* Системы интерактивной документации.
|
||||
* Системы автоматической генерации клиентского кода для многих языков.
|
||||
* Обеспечит 2 интерактивных веб-интерфейса документации напрямую.
|
||||
* Поскольку параметр `q` объявлен с `= None`, он необязателен.
|
||||
* Без `None` он был бы обязательным (как тело запроса в случае с `PUT`).
|
||||
* Для `PUT` запросов к `/items/{item_id}` читать тело запроса как JSON:
|
||||
* Проверять, что есть обязательный атрибут `name`, который должен быть `str`.
|
||||
* Проверять, что есть обязательный атрибут `price`, который должен быть `float`.
|
||||
* Проверять, что есть необязательный атрибут `is_offer`, который должен быть `bool`, если он присутствует.
|
||||
* Всё это также будет работать для глубоко вложенных объектов JSON.
|
||||
* Автоматически преобразовывать из и в JSON.
|
||||
* Документировать всё с помощью OpenAPI, что может быть использовано:
|
||||
* Системами интерактивной документации.
|
||||
* Системами автоматической генерации клиентского кода для многих языков.
|
||||
* Предоставлять 2 веб-интерфейса интерактивной документации напрямую.
|
||||
|
||||
---
|
||||
|
||||
Мы только немного копнули поверхность, но вы уже поняли, как все это работает.
|
||||
Мы только поверхностно ознакомились, но вы уже понимаете, как всё это работает.
|
||||
|
||||
Попробуйте изменить строку с помощью:
|
||||
Попробуйте изменить строку:
|
||||
|
||||
```Python
|
||||
return {"item_name": item.name, "item_id": item_id}
|
||||
@@ -406,61 +417,85 @@ item: Item
|
||||
... "item_name": item.name ...
|
||||
```
|
||||
|
||||
...в:
|
||||
...на:
|
||||
|
||||
```Python
|
||||
... "item_price": item.price ...
|
||||
```
|
||||
|
||||
... и посмотрите, как ваш редактор будет автоматически заполнять атрибуты и узнавать их типы:
|
||||
...и посмотрите, как ваш редактор кода будет автоматически дополнять атрибуты и знать их типы:
|
||||
|
||||

|
||||
|
||||
Более полный пример с дополнительными функциями см. в <a href="https://fastapi.tiangolo.com/tutorial/">Учебное руководство - Руководство пользователя</a>.
|
||||
Более полный пример с дополнительными возможностями см. в <a href="https://fastapi.tiangolo.com/ru/tutorial/">Учебник - Руководство пользователя</a>.
|
||||
|
||||
**Осторожно, спойлер**: руководство пользователя включает в себя:
|
||||
**Осторожно, спойлер**: учебник - руководство включает:
|
||||
|
||||
* Объявление **параметров** из других мест, таких как: **заголовки**, **cookies**, **поля формы** и **файлы**.
|
||||
* Как установить **ограничительные проверки** такие как `maximum_length` или `regex`.
|
||||
* Очень мощная и простая в использовании система **<abbr title="также известная как компоненты, ресурсы, провайдеры, сервисы, инъекции">внедрения зависимостей</abbr>**.
|
||||
* Безопасность и аутентификация, включая поддержку **OAuth2** с **токенами JWT** и **HTTP Basic** аутентификацию.
|
||||
* Более продвинутые (но столь же простые) методы объявления **глубоко вложенных моделей JSON** (спасибо Pydantic).
|
||||
* **GraphQL** интеграция с <a href="https://strawberry.rocks" class="external-link" target="_blank">Strawberry</a> и другими библиотеками.
|
||||
* Объявление **параметров** из других источников: **HTTP-заголовки**, **cookies**, **поля формы** и **файлы**.
|
||||
* Как задать **ограничения валидации** вроде `maximum_length` или `regex`.
|
||||
* Очень мощную и простую в использовании систему **<abbr title="также известная как: компоненты, ресурсы, провайдеры, сервисы, инъекции">внедрения зависимостей</abbr>**.
|
||||
* Безопасность и аутентификацию, включая поддержку **OAuth2** с **JWT токенами** и **HTTP Basic** аутентификацию.
|
||||
* Более продвинутые (но столь же простые) приёмы объявления **глубоко вложенных JSON-моделей** (спасибо Pydantic).
|
||||
* Интеграцию **GraphQL** с <a href="https://strawberry.rocks" class="external-link" target="_blank">Strawberry</a> и другими библиотеками.
|
||||
* Множество дополнительных функций (благодаря Starlette), таких как:
|
||||
* **Веб-сокеты**
|
||||
* очень простые тесты на основе HTTPX и `pytest`
|
||||
* **WebSockets**
|
||||
* чрезвычайно простые тесты на основе HTTPX и `pytest`
|
||||
* **CORS**
|
||||
* **Cookie сеансы(сессии)**
|
||||
* **сессии с использованием cookie**
|
||||
* ...и многое другое.
|
||||
|
||||
## Производительность
|
||||
## Производительность { #performance }
|
||||
|
||||
Независимые тесты TechEmpower показывают приложения **FastAPI**, работающие под управлением Uvicorn, как <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" class="external-link" target="_blank">один из самых быстрых доступных фреймворков Python</a>, уступающий только самим Starlette и Uvicorn (используемых внутри FastAPI). (*)
|
||||
Независимые бенчмарки TechEmpower показывают приложения **FastAPI**, работающие под управлением Uvicorn, как <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" class="external-link" target="_blank">один из самых быстрых доступных фреймворков Python</a>, уступающий только самим Starlette и Uvicorn (используются внутри FastAPI). (*)
|
||||
|
||||
Чтобы узнать больше об этом, см. раздел <a href="https://fastapi.tiangolo.com/benchmarks/" class="internal-link" target="_blank">Тесты производительности</a>.
|
||||
Чтобы узнать больше, см. раздел <a href="https://fastapi.tiangolo.com/ru/benchmarks/" class="internal-link" target="_blank">Бенчмарки</a>.
|
||||
|
||||
## Необязательные зависимости
|
||||
## Зависимости { #dependencies }
|
||||
|
||||
FastAPI зависит от Pydantic и Starlette.
|
||||
|
||||
### Зависимости `standard` { #standard-dependencies }
|
||||
|
||||
Когда вы устанавливаете FastAPI с помощью `pip install "fastapi[standard]"`, он идёт с группой опциональных зависимостей `standard`:
|
||||
|
||||
Используется Pydantic:
|
||||
|
||||
* <a href="https://github.com/JoshData/python-email-validator" target="_blank"><code>email-validator</code></a> - для проверки электронной почты.
|
||||
* <a href="https://github.com/JoshData/python-email-validator" target="_blank"><code>email-validator</code></a> — для проверки адресов электронной почты.
|
||||
|
||||
Используется Starlette:
|
||||
|
||||
* <a href="https://www.python-httpx.org" target="_blank"><code>HTTPX</code></a> - Обязательно, если вы хотите использовать `TestClient`.
|
||||
* <a href="https://jinja.palletsprojects.com" target="_blank"><code>jinja2</code></a> - Обязательно, если вы хотите использовать конфигурацию шаблона по умолчанию.
|
||||
* <a href="https://github.com/Kludex/python-multipart" target="_blank"><code>python-multipart</code></a> - Обязательно, если вы хотите поддерживать форму <abbr title="преобразование строки, полученной из HTTP-запроса, в данные Python">"парсинга"</abbr> с помощью `request.form()`.
|
||||
* <a href="https://pythonhosted.org/itsdangerous/" target="_blank"><code>itsdangerous</code></a> - Обязательно, для поддержки `SessionMiddleware`.
|
||||
* <a href="https://pyyaml.org/wiki/PyYAMLDocumentation" target="_blank"><code>pyyaml</code></a> - Обязательно, для поддержки `SchemaGenerator` Starlette (возможно, вам это не нужно с FastAPI).
|
||||
* <a href="https://www.python-httpx.org" target="_blank"><code>httpx</code></a> — обязателен, если вы хотите использовать `TestClient`.
|
||||
* <a href="https://jinja.palletsprojects.com" target="_blank"><code>jinja2</code></a> — обязателен, если вы хотите использовать конфигурацию шаблонов по умолчанию.
|
||||
* <a href="https://github.com/Kludex/python-multipart" target="_blank"><code>python-multipart</code></a> — обязателен, если вы хотите поддерживать <abbr title="преобразование строки, полученной из HTTP-запроса, в данные Python">«парсинг»</abbr> форм через `request.form()`.
|
||||
|
||||
Используется FastAPI / Starlette:
|
||||
Используется FastAPI:
|
||||
|
||||
* <a href="https://www.uvicorn.org" target="_blank"><code>uvicorn</code></a> - сервер, который загружает и обслуживает ваше приложение.
|
||||
* <a href="https://github.com/ijl/orjson" target="_blank"><code>orjson</code></a> - Обязательно, если вы хотите использовать `ORJSONResponse`.
|
||||
* <a href="https://github.com/esnme/ultrajson" target="_blank"><code>ujson</code></a> - Обязательно, если вы хотите использовать `UJSONResponse`.
|
||||
* <a href="https://www.uvicorn.org" target="_blank"><code>uvicorn</code></a> — сервер, который загружает и обслуживает ваше приложение. Включает `uvicorn[standard]`, содержащий некоторые зависимости (например, `uvloop`), нужные для высокой производительности.
|
||||
* `fastapi-cli[standard]` — чтобы предоставить команду `fastapi`.
|
||||
* Включает `fastapi-cloud-cli`, который позволяет развернуть ваше приложение FastAPI в <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>.
|
||||
|
||||
Вы можете установить все это с помощью `pip install "fastapi[all]"`.
|
||||
### Без зависимостей `standard` { #without-standard-dependencies }
|
||||
|
||||
## Лицензия
|
||||
Если вы не хотите включать опциональные зависимости `standard`, можно установить `pip install fastapi` вместо `pip install "fastapi[standard]"`.
|
||||
|
||||
### Без `fastapi-cloud-cli` { #without-fastapi-cloud-cli }
|
||||
|
||||
Если вы хотите установить FastAPI со стандартными зависимостями, но без `fastapi-cloud-cli`, установите `pip install "fastapi[standard-no-fastapi-cloud-cli]"`.
|
||||
|
||||
### Дополнительные опциональные зависимости { #additional-optional-dependencies }
|
||||
|
||||
Есть дополнительные зависимости, которые вы можете установить.
|
||||
|
||||
Дополнительные опциональные зависимости Pydantic:
|
||||
|
||||
* <a href="https://docs.pydantic.dev/latest/usage/pydantic_settings/" target="_blank"><code>pydantic-settings</code></a> — для управления настройками.
|
||||
* <a href="https://docs.pydantic.dev/latest/usage/types/extra_types/extra_types/" target="_blank"><code>pydantic-extra-types</code></a> — дополнительные типы для использования с Pydantic.
|
||||
|
||||
Дополнительные опциональные зависимости FastAPI:
|
||||
|
||||
* <a href="https://github.com/ijl/orjson" target="_blank"><code>orjson</code></a> — обязателен, если вы хотите использовать `ORJSONResponse`.
|
||||
* <a href="https://github.com/esnme/ultrajson" target="_blank"><code>ujson</code></a> — обязателен, если вы хотите использовать `UJSONResponse`.
|
||||
|
||||
## Лицензия { #license }
|
||||
|
||||
Этот проект распространяется на условиях лицензии MIT.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Обучение
|
||||
# Обучение { #learn }
|
||||
|
||||
Здесь представлены вводные разделы и учебные пособия для изучения **FastAPI**.
|
||||
|
||||
|
||||
@@ -1,84 +1,28 @@
|
||||
# Генераторы проектов - Шаблоны
|
||||
# Шаблон Full Stack FastAPI { #full-stack-fastapi-template }
|
||||
|
||||
Чтобы начать работу быстрее, Вы можете использовать "генераторы проектов", в которые включены множество начальных настроек для функций безопасности, баз данных и некоторые <dfn title="также известные как маршруты, URLы, ручки, ">эндпоинты</dfn> API.
|
||||
Шаблоны, хотя обычно поставляются с определённой конфигурацией, спроектированы так, чтобы быть гибкими и настраиваемыми. Это позволяет вам изменять их и адаптировать под требования вашего проекта, что делает их отличной отправной точкой. 🏁
|
||||
|
||||
В генераторе проектов всегда будут предустановлены какие-то настройки, которые Вам следует обновить и подогнать под свои нужды, но это может быть хорошей отправной точкой для Вашего проекта.
|
||||
Вы можете использовать этот шаблон для старта: в нём уже сделана значительная часть начальной настройки, безопасность, база данных и несколько эндпоинтов API.
|
||||
|
||||
## Full Stack FastAPI PostgreSQL
|
||||
Репозиторий GitHub: <a href="https://github.com/tiangolo/full-stack-fastapi-template" class="external-link" target="_blank">Full Stack FastAPI Template</a>
|
||||
|
||||
GitHub: <a href="https://github.com/tiangolo/full-stack-fastapi-postgresql" class="external-link" target="_blank">https://github.com/tiangolo/full-stack-fastapi-postgresql</a>
|
||||
## Шаблон Full Stack FastAPI — Технологический стек и возможности { #full-stack-fastapi-template-technology-stack-and-features }
|
||||
|
||||
### Full Stack FastAPI PostgreSQL - Особенности
|
||||
|
||||
* Полностью интегрирован с **Docker** (основан на Docker).
|
||||
* Развёртывается в режиме Docker Swarm.
|
||||
* Интегрирован с **Docker Compose** и оптимизирован для локальной разработки.
|
||||
* **Готовый к реальной работе** веб-сервер Python использующий Uvicorn и Gunicorn.
|
||||
* Бэкенд построен на фреймворке <a href="https://github.com/fastapi/fastapi" class="external-link" target="_blank">**FastAPI**</a>:
|
||||
* **Быстрый**: Высокопроизводительный, на уровне **NodeJS** и **Go** (благодаря Starlette и Pydantic).
|
||||
* **Интуитивно понятный**: Отличная поддержка редактора. <dfn title="также известное как автозаполнение, интеллектуальность, автозавершение">Автодополнение кода</dfn> везде. Меньше времени на отладку.
|
||||
* **Простой**: Разработан так, чтоб быть простым в использовании и изучении. Меньше времени на чтение документации.
|
||||
* **Лаконичный**: Минимизировано повторение кода. Каждый объявленный параметр определяет несколько функций.
|
||||
* **Надёжный**: Получите готовый к работе код. С автоматической интерактивной документацией.
|
||||
* **Стандартизированный**: Основан на открытых стандартах API (<a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> и <a href="https://json-schema.org/" class="external-link" target="_blank">JSON Schema</a>) и полностью совместим с ними.
|
||||
* <a href="https://fastapi.tiangolo.com/features/" class="external-link" target="_blank">**Множество других возможностей**</a> включая автоматическую проверку и сериализацию данных, интерактивную документацию, аутентификацию с помощью OAuth2 JWT-токенов и т.д.
|
||||
* **Безопасное хранение паролей**, которые хэшируются по умолчанию.
|
||||
* Аутентификация посредством **JWT-токенов**.
|
||||
* <dfn title="Python-объекты связанные с базами данных">Модели</dfn> **SQLAlchemy** (независящие от расширений Flask, а значит могут быть непосредственно использованы процессами Celery).
|
||||
* Базовая модель пользователя (измените или удалите её по необходимости).
|
||||
* **Alembic** для организации миграций.
|
||||
* **CORS** (Совместное использование ресурсов из разных источников).
|
||||
* **Celery**, процессы которого могут выборочно импортировать и использовать модели и код из остальной части бэкенда.
|
||||
* Тесты, на основе **Pytest**, интегрированные в Docker, чтобы Вы могли полностью проверить Ваше API, независимо от базы данных. Так как тесты запускаются в Docker, для них может создаваться новое хранилище данных каждый раз (Вы можете, по своему желанию, использовать ElasticSearch, MongoDB, CouchDB или другую СУБД, только лишь для проверки - будет ли Ваше API работать с этим хранилищем).
|
||||
* Простая интеграция Python с **Jupyter Kernels** для разработки удалённо или в Docker с расширениями похожими на Atom Hydrogen или Visual Studio Code Jupyter.
|
||||
* Фронтенд построен на фреймворке **Vue**:
|
||||
* Сгенерирован с помощью Vue CLI.
|
||||
* Поддерживает **аутентификацию с помощью JWT-токенов**.
|
||||
* Страница логина.
|
||||
* Перенаправление на страницу главной панели мониторинга после логина.
|
||||
* Главная страница мониторинга с возможностью создания и изменения пользователей.
|
||||
* Пользователь может изменять свои данные.
|
||||
* **Vuex**.
|
||||
* **Vue-router**.
|
||||
* **Vuetify** для конструирования красивых компонентов страниц.
|
||||
* **TypeScript**.
|
||||
* Сервер Docker основан на **Nginx** (настроен для удобной работы с Vue-router).
|
||||
* Многоступенчатая сборка Docker, то есть Вам не нужно сохранять или коммитить скомпилированный код.
|
||||
* Тесты фронтенда запускаются во время сборки (можно отключить).
|
||||
* Сделан настолько модульно, насколько возможно, поэтому работает "из коробки", но Вы можете повторно сгенерировать фронтенд с помощью Vue CLI или создать то, что Вам нужно и повторно использовать то, что захотите.
|
||||
* **PGAdmin** для СУБД PostgreSQL, которые легко можно заменить на PHPMyAdmin и MySQL.
|
||||
* **Flower** для отслеживания работы Celery.
|
||||
* Балансировка нагрузки между фронтендом и бэкендом с помощью **Traefik**, а значит, Вы можете расположить их на одном домене, разделив url-пути, так как они обслуживаются разными контейнерами.
|
||||
* Интеграция с Traefik включает автоматическую генерацию сертификатов Let's Encrypt для поддержки протокола **HTTPS**.
|
||||
* GitLab **CI** (непрерывная интеграция), которая включает тестирование фронтенда и бэкенда.
|
||||
|
||||
## Full Stack FastAPI Couchbase
|
||||
|
||||
GitHub: <a href="https://github.com/tiangolo/full-stack-fastapi-couchbase" class="external-link" target="_blank">https://github.com/tiangolo/full-stack-fastapi-couchbase</a>
|
||||
|
||||
⚠️ **ПРЕДУПРЕЖДЕНИЕ** ⚠️
|
||||
|
||||
Если Вы начинаете новый проект, ознакомьтесь с представленными здесь альтернативами.
|
||||
|
||||
Например, генератор проектов <a href="https://github.com/tiangolo/full-stack-fastapi-postgresql" class="external-link" target="_blank">Full Stack FastAPI PostgreSQL</a> может быть более подходящей альтернативой, так как он активно поддерживается и используется. И он включает в себя все новые возможности и улучшения.
|
||||
|
||||
Но никто не запрещает Вам использовать генератор с СУБД Couchbase, возможно, он всё ещё работает нормально. Или у Вас уже есть проект, созданный с помощью этого генератора ранее, и Вы, вероятно, уже обновили его в соответствии со своими потребностями.
|
||||
|
||||
Вы можете прочитать о нём больше в документации соответствующего репозитория.
|
||||
|
||||
## Full Stack FastAPI MongoDB
|
||||
|
||||
...может быть когда-нибудь появится, в зависимости от наличия у меня свободного времени и прочих факторов. 😅 🎉
|
||||
|
||||
## Модели машинного обучения на основе spaCy и FastAPI
|
||||
|
||||
GitHub: <a href="https://github.com/microsoft/cookiecutter-spacy-fastapi" class="external-link" target="_blank">https://github.com/microsoft/cookiecutter-spacy-fastapi</a>
|
||||
|
||||
### Модели машинного обучения на основе spaCy и FastAPI - Особенности
|
||||
|
||||
* Интеграция с моделями **spaCy** NER.
|
||||
* Встроенный формат запросов к **когнитивному поиску Azure**.
|
||||
* **Готовый к реальной работе** веб-сервер Python использующий Uvicorn и Gunicorn.
|
||||
* Встроенное развёртывание на основе **Azure DevOps** Kubernetes (AKS) CI/CD.
|
||||
* **Многоязычность**. Лёгкий выбор одного из встроенных в spaCy языков во время настройки проекта.
|
||||
* **Легко подключить** модели из других фреймворков (Pytorch, Tensorflow) не ограничиваясь spaCy.
|
||||
- ⚡ [**FastAPI**](https://fastapi.tiangolo.com/ru) для бэкенд‑API на Python.
|
||||
- 🧰 [SQLModel](https://sqlmodel.tiangolo.com) для взаимодействия с SQL‑базой данных на Python (ORM).
|
||||
- 🔍 [Pydantic](https://docs.pydantic.dev), используется FastAPI, для валидации данных и управления настройками.
|
||||
- 💾 [PostgreSQL](https://www.postgresql.org) в качестве SQL‑базы данных.
|
||||
- 🚀 [React](https://react.dev) для фронтенда.
|
||||
- 💃 Используются TypeScript, хуки, [Vite](https://vitejs.dev) и другие части современного фронтенд‑стека.
|
||||
- 🎨 [Chakra UI](https://chakra-ui.com) для компонентов фронтенда.
|
||||
- 🤖 Автоматически сгенерированный фронтенд‑клиент.
|
||||
- 🧪 [Playwright](https://playwright.dev) для End‑to‑End тестирования.
|
||||
- 🦇 Поддержка тёмной темы.
|
||||
- 🐋 [Docker Compose](https://www.docker.com) для разработки и продакшна.
|
||||
- 🔒 Безопасное хэширование паролей по умолчанию.
|
||||
- 🔑 Аутентификация по JWT‑токенам.
|
||||
- 📫 Восстановление пароля по электронной почте.
|
||||
- ✅ Тесты с [Pytest](https://pytest.org).
|
||||
- 📞 [Traefik](https://traefik.io) в роли обратного прокси / балансировщика нагрузки.
|
||||
- 🚢 Инструкции по развёртыванию с использованием Docker Compose, включая настройку фронтенд‑прокси Traefik для автоматического получения сертификатов HTTPS.
|
||||
- 🏭 CI (continuous integration) и CD (continuous deployment) на основе GitHub Actions.
|
||||
|
||||
@@ -1,30 +1,29 @@
|
||||
# Введение в аннотации типов Python
|
||||
# Введение в типы Python { #python-types-intro }
|
||||
|
||||
Python имеет поддержку необязательных аннотаций типов.
|
||||
Python поддерживает необязательные «подсказки типов» (их также называют «аннотациями типов»).
|
||||
|
||||
**Аннотации типов** являются специальным синтаксисом, который позволяет определять <abbr title="например: str, int, float, bool">тип</abbr> переменной.
|
||||
Эти **«подсказки типов»** или аннотации — это специальный синтаксис, позволяющий объявлять <abbr title="например: str, int, float, bool">тип</abbr> переменной.
|
||||
|
||||
Объявление типов для переменных позволяет улучшить поддержку вашего кода редакторами и различными инструментами.
|
||||
Объявляя типы для ваших переменных, редакторы кода и инструменты смогут лучше вас поддерживать.
|
||||
|
||||
Это просто **краткое руководство / напоминание** об аннотациях типов в Python. Оно охватывает только минимум, необходимый для их использования с **FastAPI**... что на самом деле очень мало.
|
||||
Это всего лишь **краткое руководство / напоминание** о подсказках типов в Python. Оно охватывает только минимум, необходимый для их использования с **FastAPI**... что на самом деле очень мало.
|
||||
|
||||
**FastAPI** целиком основан на аннотациях типов, у них много выгод и преимуществ.
|
||||
**FastAPI** целиком основан на этих подсказках типов — они дают ему множество преимуществ и выгод.
|
||||
|
||||
Но даже если вы никогда не используете **FastAPI**, вам будет полезно немного узнать о них.
|
||||
|
||||
/// note
|
||||
/// note | Примечание
|
||||
|
||||
Если вы являетесь экспертом в Python и уже знаете всё об аннотациях типов, переходите к следующему разделу.
|
||||
Если вы являетесь экспертом в Python и уже знаете всё о подсказках типов, переходите к следующей главе.
|
||||
|
||||
///
|
||||
|
||||
## Мотивация
|
||||
## Мотивация { #motivation }
|
||||
|
||||
Давайте начнем с простого примера:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial001.py *}
|
||||
|
||||
|
||||
Вызов этой программы выводит:
|
||||
|
||||
```
|
||||
@@ -34,13 +33,12 @@ John Doe
|
||||
Функция делает следующее:
|
||||
|
||||
* Принимает `first_name` и `last_name`.
|
||||
* Преобразует первую букву содержимого каждой переменной в верхний регистр с `title()`.
|
||||
* <abbr title="Объединяет в одно целое, последовательно, друг за другом.">Соединяет</abbr> их через пробел.
|
||||
* Преобразует первую букву каждого значения в верхний регистр с помощью `title()`.
|
||||
* <abbr title="Объединяет в одно целое. Содержимое одного — сразу после другого.">Соединяет</abbr> их пробелом посередине.
|
||||
|
||||
{* ../../docs_src/python_types/tutorial001.py hl[2] *}
|
||||
|
||||
|
||||
### Отредактируем пример
|
||||
### Отредактируем пример { #edit-it }
|
||||
|
||||
Это очень простая программа.
|
||||
|
||||
@@ -48,23 +46,23 @@ John Doe
|
||||
|
||||
В какой-то момент вы бы начали определение функции, у вас были бы готовы параметры...
|
||||
|
||||
Но затем вы должны вызвать «тот метод, который преобразует первую букву в верхний регистр».
|
||||
Но затем нужно вызвать «тот метод, который делает первую букву заглавной».
|
||||
|
||||
Было это `upper`? Или `uppercase`? `first_uppercase`? `capitalize`?
|
||||
Это был `upper`? Или `uppercase`? `first_uppercase`? `capitalize`?
|
||||
|
||||
Тогда вы попробуете с давним другом программиста: автодополнением редактора.
|
||||
Тогда вы пробуете старого друга программиста — автозавершение редактора кода.
|
||||
|
||||
Вы вводите первый параметр функции, `first_name`, затем точку (`.`), а затем нажимаете `Ctrl+Space`, чтобы запустить дополнение.
|
||||
Вы вводите первый параметр функции, `first_name`, затем точку (`.`) и нажимаете `Ctrl+Space`, чтобы вызвать автозавершение.
|
||||
|
||||
Но, к сожалению, ничего полезного не выходит:
|
||||
Но, к сожалению, ничего полезного не находится:
|
||||
|
||||
<img src="/img/python-types/image01.png">
|
||||
|
||||
### Добавим типы
|
||||
### Добавим типы { #add-types }
|
||||
|
||||
Давайте изменим одну строчку в предыдущей версии.
|
||||
Давайте изменим одну строку из предыдущей версии.
|
||||
|
||||
Мы изменим именно этот фрагмент, параметры функции, с:
|
||||
Мы поменяем ровно этот фрагмент — параметры функции — с:
|
||||
|
||||
```Python
|
||||
first_name, last_name
|
||||
@@ -76,14 +74,13 @@ John Doe
|
||||
first_name: str, last_name: str
|
||||
```
|
||||
|
||||
Вот и все.
|
||||
Вот и всё.
|
||||
|
||||
Это аннотации типов:
|
||||
Это и есть «подсказки типов»:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial002.py hl[1] *}
|
||||
|
||||
|
||||
Это не то же самое, что объявление значений по умолчанию, например:
|
||||
Это не то же самое, что объявление значений по умолчанию, как, например:
|
||||
|
||||
```Python
|
||||
first_name="john", last_name="doe"
|
||||
@@ -91,47 +88,45 @@ John Doe
|
||||
|
||||
Это другая вещь.
|
||||
|
||||
Мы используем двоеточия (`:`), а не равно (`=`).
|
||||
Здесь мы используем двоеточия (`:`), а не знак равенства (`=`).
|
||||
|
||||
И добавление аннотаций типов обычно не меняет происходящего по сравнению с тем, что произошло бы без неё.
|
||||
И добавление подсказок типов обычно не меняет поведение программы по сравнению с вариантом без них.
|
||||
|
||||
Но теперь представьте, что вы снова находитесь в процессе создания этой функции, но уже с аннотациями типов.
|
||||
Но теперь представьте, что вы снова посередине написания этой функции, только уже с подсказками типов.
|
||||
|
||||
В тот же момент вы пытаетесь запустить автодополнение с помощью `Ctrl+Space` и вы видите:
|
||||
В тот же момент вы пробуете вызвать автозавершение с помощью `Ctrl+Space` — и видите:
|
||||
|
||||
<img src="/img/python-types/image02.png">
|
||||
|
||||
При этом вы можете просматривать варианты, пока не найдёте подходящий:
|
||||
С этим вы можете прокручивать варианты, пока не найдёте тот самый:
|
||||
|
||||
<img src="/img/python-types/image03.png">
|
||||
|
||||
## Больше мотивации
|
||||
## Больше мотивации { #more-motivation }
|
||||
|
||||
Проверьте эту функцию, она уже имеет аннотации типов:
|
||||
Посмотрите на эту функцию — у неё уже есть подсказки типов:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial003.py hl[1] *}
|
||||
|
||||
|
||||
Поскольку редактор знает типы переменных, вы получаете не только дополнение, но и проверки ошибок:
|
||||
Так как редактор кода знает типы переменных, вы получаете не только автозавершение, но и проверки ошибок:
|
||||
|
||||
<img src="/img/python-types/image04.png">
|
||||
|
||||
Теперь вы знаете, что вам нужно исправить, преобразовав `age` в строку с `str(age)`:
|
||||
Теперь вы знаете, что нужно исправить — преобразовать `age` в строку с помощью `str(age)`:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial004.py hl[2] *}
|
||||
|
||||
## Объявление типов { #declaring-types }
|
||||
|
||||
## Объявление типов
|
||||
Вы только что увидели основное место, где объявляют подсказки типов — параметры функции.
|
||||
|
||||
Вы только что видели основное место для объявления подсказок типов. В качестве параметров функции.
|
||||
Это также основное место, где вы будете использовать их с **FastAPI**.
|
||||
|
||||
Это также основное место, где вы можете использовать их с **FastAPI**.
|
||||
### Простые типы { #simple-types }
|
||||
|
||||
### Простые типы
|
||||
Вы можете объявлять все стандартные типы Python, не только `str`.
|
||||
|
||||
Вы можете объявить все стандартные типы Python, а не только `str`.
|
||||
|
||||
Вы можете использовать, к примеру:
|
||||
Можно использовать, например:
|
||||
|
||||
* `int`
|
||||
* `float`
|
||||
@@ -140,143 +135,361 @@ John Doe
|
||||
|
||||
{* ../../docs_src/python_types/tutorial005.py hl[1] *}
|
||||
|
||||
### Generic-типы с параметрами типов { #generic-types-with-type-parameters }
|
||||
|
||||
### Generic-типы с параметрами типов
|
||||
Есть структуры данных, которые могут содержать другие значения, например, `dict`, `list`, `set` и `tuple`. И внутренние значения тоже могут иметь свой тип.
|
||||
|
||||
Существуют некоторые структуры данных, которые могут содержать другие значения, например, `dict`, `list`, `set` и `tuple`. И внутренние значения тоже могут иметь свой тип.
|
||||
Такие типы, которые содержат внутренние типы, называют «**generic**»-типами. И их можно объявлять, в том числе с указанием внутренних типов.
|
||||
|
||||
Чтобы объявить эти типы и внутренние типы, вы можете использовать стандартный Python-модуль `typing`.
|
||||
Чтобы объявлять эти типы и их внутренние типы, вы можете использовать стандартный модуль Python `typing`. Он существует специально для поддержки подсказок типов.
|
||||
|
||||
Он существует специально для поддержки подсказок этих типов.
|
||||
#### Новые версии Python { #newer-versions-of-python }
|
||||
|
||||
#### `List`
|
||||
Синтаксис с использованием `typing` **совместим** со всеми версиями, от Python 3.6 до самых новых, включая Python 3.9, Python 3.10 и т.д.
|
||||
|
||||
Например, давайте определим переменную как `list`, состоящий из `str`.
|
||||
По мере развития Python **новые версии** получают улучшенную поддержку этих аннотаций типов, и во многих случаях вам даже не нужно импортировать и использовать модуль `typing`, чтобы объявлять аннотации типов.
|
||||
|
||||
Импортируйте `List` из `typing` (с заглавной `L`):
|
||||
Если вы можете выбрать более свежую версию Python для проекта, вы получите дополнительную простоту.
|
||||
|
||||
{* ../../docs_src/python_types/tutorial006.py hl[1] *}
|
||||
Во всей документации есть примеры, совместимые с каждой версией Python (когда есть различия).
|
||||
|
||||
Например, «**Python 3.6+**» означает совместимость с Python 3.6 и выше (включая 3.7, 3.8, 3.9, 3.10 и т.д.). А «**Python 3.9+**» — совместимость с Python 3.9 и выше (включая 3.10 и т.п.).
|
||||
|
||||
Если вы можете использовать **последние версии Python**, используйте примеры для самой новой версии — у них будет **самый лучший и простой синтаксис**, например, «**Python 3.10+**».
|
||||
|
||||
#### List { #list }
|
||||
|
||||
Например, давайте определим переменную как `list` из `str`.
|
||||
|
||||
//// tab | Python 3.9+
|
||||
|
||||
Объявите переменную с тем же синтаксисом двоеточия (`:`).
|
||||
|
||||
В качестве типа укажите `List`.
|
||||
В качестве типа укажите `list`.
|
||||
|
||||
Поскольку список является типом, содержащим некоторые внутренние типы, вы помещаете их в квадратные скобки:
|
||||
Так как список — это тип, содержащий внутренние типы, укажите их в квадратных скобках:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial006.py hl[4] *}
|
||||
```Python hl_lines="1"
|
||||
{!> ../../docs_src/python_types/tutorial006_py39.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
/// tip
|
||||
//// tab | Python 3.8+
|
||||
|
||||
Из `typing` импортируйте `List` (с заглавной `L`):
|
||||
|
||||
```Python hl_lines="1"
|
||||
{!> ../../docs_src/python_types/tutorial006.py!}
|
||||
```
|
||||
|
||||
Объявите переменную с тем же синтаксисом двоеточия (`:`).
|
||||
|
||||
В качестве типа используйте `List`, который вы импортировали из `typing`.
|
||||
|
||||
Так как список — это тип, содержащий внутренние типы, укажите их в квадратных скобках:
|
||||
|
||||
```Python hl_lines="4"
|
||||
{!> ../../docs_src/python_types/tutorial006.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
/// info | Информация
|
||||
|
||||
Эти внутренние типы в квадратных скобках называются «параметрами типов».
|
||||
|
||||
В этом случае `str` является параметром типа, передаваемым в `List`.
|
||||
В данном случае `str` — это параметр типа, передаваемый в `List` (или `list` в Python 3.9 и выше).
|
||||
|
||||
///
|
||||
|
||||
Это означает: "переменная `items` является `list`, и каждый из элементов этого списка является `str`".
|
||||
Это означает: «переменная `items` — это `list`, и каждый элемент этого списка — `str`».
|
||||
|
||||
Если вы будете так поступать, редактор может оказывать поддержку даже при обработке элементов списка:
|
||||
/// tip | Совет
|
||||
|
||||
Если вы используете Python 3.9 или выше, вам не нужно импортировать `List` из `typing`, можно использовать обычный встроенный тип `list`.
|
||||
|
||||
///
|
||||
|
||||
Таким образом, ваш редактор кода сможет помогать даже при обработке элементов списка:
|
||||
|
||||
<img src="/img/python-types/image05.png">
|
||||
|
||||
Без типов добиться этого практически невозможно.
|
||||
Без типов добиться этого почти невозможно.
|
||||
|
||||
Обратите внимание, что переменная `item` является одним из элементов списка `items`.
|
||||
Обратите внимание, что переменная `item` — один из элементов списка `items`.
|
||||
|
||||
И все же редактор знает, что это `str`, и поддерживает это.
|
||||
И всё же редактор кода знает, что это `str`, и даёт соответствующую поддержку.
|
||||
|
||||
#### `Tuple` и `Set`
|
||||
#### Tuple и Set { #tuple-and-set }
|
||||
|
||||
Вы бы сделали то же самое, чтобы объявить `tuple` и `set`:
|
||||
Аналогично вы бы объявили `tuple` и `set`:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial007.py hl[1,4] *}
|
||||
//// tab | Python 3.9+
|
||||
|
||||
```Python hl_lines="1"
|
||||
{!> ../../docs_src/python_types/tutorial007_py39.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+
|
||||
|
||||
```Python hl_lines="1 4"
|
||||
{!> ../../docs_src/python_types/tutorial007.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
Это означает:
|
||||
|
||||
* Переменная `items_t` является `tuple` с 3 элементами: `int`, другим `int` и `str`.
|
||||
* Переменная `items_s` является `set` и каждый элемент имеет тип `bytes`.
|
||||
* Переменная `items_t` — это `tuple` из 3 элементов: `int`, ещё один `int` и `str`.
|
||||
* Переменная `items_s` — это `set`, и каждый элемент имеет тип `bytes`.
|
||||
|
||||
#### `Dict`
|
||||
#### Dict { #dict }
|
||||
|
||||
Чтобы определить `dict`, вы передаёте 2 параметра типов, разделённых запятыми.
|
||||
Чтобы определить `dict`, вы передаёте 2 параметра типов, разделённые запятой.
|
||||
|
||||
Первый параметр типа предназначен для ключей `dict`.
|
||||
Первый параметр типа — для ключей `dict`.
|
||||
|
||||
Второй параметр типа предназначен для значений `dict`:
|
||||
Второй параметр типа — для значений `dict`:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial008.py hl[1,4] *}
|
||||
//// tab | Python 3.9+
|
||||
|
||||
```Python hl_lines="1"
|
||||
{!> ../../docs_src/python_types/tutorial008_py39.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+
|
||||
|
||||
```Python hl_lines="1 4"
|
||||
{!> ../../docs_src/python_types/tutorial008.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
Это означает:
|
||||
|
||||
* Переменная `prices` является `dict`:
|
||||
* Ключи этого `dict` имеют тип `str` (скажем, название каждого элемента).
|
||||
* Переменная `prices` — это `dict`:
|
||||
* Ключи этого `dict` имеют тип `str` (скажем, название каждой позиции).
|
||||
* Значения этого `dict` имеют тип `float` (скажем, цена каждой позиции).
|
||||
|
||||
#### `Optional`
|
||||
#### Union { #union }
|
||||
|
||||
Вы также можете использовать `Optional`, чтобы объявить, что переменная имеет тип, например, `str`, но это является «необязательным», что означает, что она также может быть `None`:
|
||||
Вы можете объявить, что переменная может быть **одним из нескольких типов**, например, `int` или `str`.
|
||||
|
||||
В Python 3.6 и выше (включая Python 3.10) вы можете использовать тип `Union` из `typing` и перечислить в квадратных скобках все допустимые типы.
|
||||
|
||||
В Python 3.10 также появился **новый синтаксис**, где допустимые типы можно указать через <abbr title='также называется «побитовый оператор OR», но это значение здесь нерелевантно'>вертикальную черту (`|`)</abbr>.
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python hl_lines="1"
|
||||
{!> ../../docs_src/python_types/tutorial008b_py310.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+
|
||||
|
||||
```Python hl_lines="1 4"
|
||||
{!> ../../docs_src/python_types/tutorial008b.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
В обоих случаях это означает, что `item` может быть `int` или `str`.
|
||||
|
||||
#### Возможно `None` { #possibly-none }
|
||||
|
||||
Вы можете объявить, что значение может иметь определённый тип, например `str`, но также может быть и `None`.
|
||||
|
||||
В Python 3.6 и выше (включая Python 3.10) это можно объявить, импортировав и используя `Optional` из модуля `typing`.
|
||||
|
||||
```Python hl_lines="1 4"
|
||||
{!../../docs_src/python_types/tutorial009.py!}
|
||||
```
|
||||
|
||||
Использование `Optional[str]` вместо просто `str` позволит редактору помочь вам в обнаружении ошибок, в которых вы могли бы предположить, что значение всегда является `str`, хотя на самом деле это может быть и `None`.
|
||||
Использование `Optional[str]` вместо просто `str` позволит редактору кода помочь вам обнаружить ошибки, когда вы предполагаете, что значение всегда `str`, хотя на самом деле оно может быть и `None`.
|
||||
|
||||
#### Generic-типы
|
||||
`Optional[Something]` — это на самом деле сокращение для `Union[Something, None]`, они эквивалентны.
|
||||
|
||||
Эти типы принимают параметры в квадратных скобках:
|
||||
Это также означает, что в Python 3.10 вы можете использовать `Something | None`:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python hl_lines="1"
|
||||
{!> ../../docs_src/python_types/tutorial009_py310.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+
|
||||
|
||||
```Python hl_lines="1 4"
|
||||
{!> ../../docs_src/python_types/tutorial009.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+ альтернативный вариант
|
||||
|
||||
```Python hl_lines="1 4"
|
||||
{!> ../../docs_src/python_types/tutorial009b.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
#### Использовать `Union` или `Optional` { #using-union-or-optional }
|
||||
|
||||
Если вы используете версию Python ниже 3.10, вот совет с моей весьма **субъективной** точки зрения:
|
||||
|
||||
* 🚨 Избегайте использования `Optional[SomeType]`
|
||||
* Вместо этого ✨ **используйте `Union[SomeType, None]`** ✨.
|
||||
|
||||
Оба варианта эквивалентны и внутри одинаковы, но я бы рекомендовал `Union` вместо `Optional`, потому что слово «**optional**» («необязательный») может навести на мысль, что значение необязательное, хотя на самом деле оно означает «может быть `None`», даже если параметр не является необязательным и всё ещё обязателен.
|
||||
|
||||
Мне кажется, `Union[SomeType, None]` более явно выражает смысл.
|
||||
|
||||
Речь только о словах и названиях. Но эти слова могут влиять на то, как вы и ваши коллеги думаете о коде.
|
||||
|
||||
В качестве примера возьмём эту функцию:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial009c.py hl[1,4] *}
|
||||
|
||||
Параметр `name` определён как `Optional[str]`, но он **не необязательный** — вы не можете вызвать функцию без этого параметра:
|
||||
|
||||
```Python
|
||||
say_hi() # О нет, это вызывает ошибку! 😱
|
||||
```
|
||||
|
||||
Параметр `name` всё ещё **обязателен** (не *optional*), потому что у него нет значения по умолчанию. При этом `name` принимает `None` как значение:
|
||||
|
||||
```Python
|
||||
say_hi(name=None) # Это работает, None допустим 🎉
|
||||
```
|
||||
|
||||
Хорошая новость: как только вы перейдёте на Python 3.10, об этом можно не переживать — вы сможете просто использовать `|` для объединения типов:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial009c_py310.py hl[1,4] *}
|
||||
|
||||
И тогда вам не придётся задумываться о названиях вроде `Optional` и `Union`. 😎
|
||||
|
||||
#### Generic-типы { #generic-types }
|
||||
|
||||
Типы, которые принимают параметры типов в квадратных скобках, называются **Generic-типами** или **Generics**, например:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
Вы можете использовать те же встроенные типы как generics (с квадратными скобками и типами внутри):
|
||||
|
||||
* `list`
|
||||
* `tuple`
|
||||
* `set`
|
||||
* `dict`
|
||||
|
||||
И, как и в Python 3.8, из модуля `typing`:
|
||||
|
||||
* `Union`
|
||||
* `Optional` (так же, как в Python 3.8)
|
||||
* ...и другие.
|
||||
|
||||
В Python 3.10, как альтернативу generics `Union` и `Optional`, можно использовать <abbr title='также называется «побитовый оператор OR», но это значение здесь нерелевантно'>вертикальную черту (`|`)</abbr> для объявления объединений типов — это гораздо лучше и проще.
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.9+
|
||||
|
||||
Вы можете использовать те же встроенные типы как generics (с квадратными скобками и типами внутри):
|
||||
|
||||
* `list`
|
||||
* `tuple`
|
||||
* `set`
|
||||
* `dict`
|
||||
|
||||
И, как и в Python 3.8, из модуля `typing`:
|
||||
|
||||
* `Union`
|
||||
* `Optional`
|
||||
* ...и другие.
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+
|
||||
|
||||
* `List`
|
||||
* `Tuple`
|
||||
* `Set`
|
||||
* `Dict`
|
||||
* `Union`
|
||||
* `Optional`
|
||||
* ...и др.
|
||||
* ...и другие.
|
||||
|
||||
называются **Generic-типами** или **Generics**.
|
||||
////
|
||||
|
||||
### Классы как типы
|
||||
### Классы как типы { #classes-as-types }
|
||||
|
||||
Вы также можете объявить класс как тип переменной.
|
||||
Вы также можете объявлять класс как тип переменной.
|
||||
|
||||
Допустим, у вас есть класс `Person` с полем `name`:
|
||||
Допустим, у вас есть класс `Person` с именем:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial010.py hl[1:3] *}
|
||||
|
||||
|
||||
Тогда вы можете объявить переменную типа `Person`:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial010.py hl[6] *}
|
||||
|
||||
|
||||
И снова вы получаете полную поддержку редактора:
|
||||
И снова вы получите полную поддержку редактора кода:
|
||||
|
||||
<img src="/img/python-types/image06.png">
|
||||
|
||||
## Pydantic-модели
|
||||
Обратите внимание, что это означает: «`one_person` — это **экземпляр** класса `Person`».
|
||||
|
||||
<a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a> является Python-библиотекой для выполнения валидации данных.
|
||||
Это не означает: «`one_person` — это **класс** с именем `Person`».
|
||||
|
||||
## Pydantic-модели { #pydantic-models }
|
||||
|
||||
<a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a> — это библиотека Python для валидации данных.
|
||||
|
||||
Вы объявляете «форму» данных как классы с атрибутами.
|
||||
|
||||
И каждый атрибут имеет тип.
|
||||
И у каждого атрибута есть тип.
|
||||
|
||||
Затем вы создаете экземпляр этого класса с некоторыми значениями, и он проверяет значения, преобразует их в соответствующий тип (если все верно) и предоставляет вам объект со всеми данными.
|
||||
Затем вы создаёте экземпляр этого класса с некоторыми значениями — он провалидирует значения, преобразует их к соответствующему типу (если это применимо) и вернёт вам объект со всеми данными.
|
||||
|
||||
И вы получаете полную поддержку редактора для этого итогового объекта.
|
||||
И вы получите полную поддержку редактора кода для этого результирующего объекта.
|
||||
|
||||
Взято из официальной документации Pydantic:
|
||||
Пример из официальной документации Pydantic:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial011.py *}
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python
|
||||
{!> ../../docs_src/python_types/tutorial011_py310.py!}
|
||||
```
|
||||
|
||||
/// info
|
||||
////
|
||||
|
||||
Чтобы узнать больше о <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic, читайте его документацию</a>.
|
||||
//// tab | Python 3.9+
|
||||
|
||||
```Python
|
||||
{!> ../../docs_src/python_types/tutorial011_py39.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+
|
||||
|
||||
```Python
|
||||
{!> ../../docs_src/python_types/tutorial011.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
/// info | Информация
|
||||
|
||||
Чтобы узнать больше о <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic, ознакомьтесь с его документацией</a>.
|
||||
|
||||
///
|
||||
|
||||
@@ -284,30 +497,80 @@ John Doe
|
||||
|
||||
Вы увидите намного больше всего этого на практике в [Руководстве пользователя](tutorial/index.md){.internal-link target=_blank}.
|
||||
|
||||
## Аннотации типов в **FastAPI**
|
||||
/// tip | Совет
|
||||
|
||||
**FastAPI** получает преимущества аннотаций типов для выполнения определённых задач.
|
||||
|
||||
С **FastAPI** вы объявляете параметры с аннотациями типов и получаете:
|
||||
|
||||
* **Поддержку редактора**.
|
||||
* **Проверки типов**.
|
||||
|
||||
...и **FastAPI** использует тот же механизм для:
|
||||
|
||||
* **Определения требований**: из параметров пути запроса, параметров запроса, заголовков, зависимостей и т.д.
|
||||
* **Преобразования данных**: от запроса к нужному типу.
|
||||
* **Валидации данных**: исходя из каждого запроса:
|
||||
* Генерации **автоматических ошибок**, возвращаемых клиенту, когда данные не являются корректными.
|
||||
* **Документирования** API с использованием OpenAPI:
|
||||
* который затем используется пользовательскими интерфейсами автоматической интерактивной документации.
|
||||
|
||||
Всё это может показаться абстрактным. Не волнуйтесь. Вы увидите всё это в действии в [Руководстве пользователя](tutorial/index.md){.internal-link target=_blank}.
|
||||
|
||||
Важно то, что при использовании стандартных типов Python в одном месте (вместо добавления дополнительных классов, декораторов и т.д.) **FastAPI** сделает за вас большую часть работы.
|
||||
|
||||
/// info
|
||||
|
||||
Если вы уже прошли всё руководство и вернулись, чтобы узнать больше о типах, хорошим ресурсом является <a href="https://mypy.readthedocs.io/en/latest/cheat_sheet_py3.html" class="external-link" target="_blank">«шпаргалка» от `mypy`</a>.
|
||||
У Pydantic есть особое поведение, когда вы используете `Optional` или `Union[Something, None]` без значения по умолчанию. Подробнее читайте в документации Pydantic: <a href="https://docs.pydantic.dev/2.3/usage/models/#required-fields" class="external-link" target="_blank">Required Optional fields</a>.
|
||||
|
||||
///
|
||||
|
||||
## Подсказки типов с аннотациями метаданных { #type-hints-with-metadata-annotations }
|
||||
|
||||
В Python также есть возможность добавлять **дополнительные <abbr title="Данные о данных, в данном случае — информация о типе, например описание.">метаданные</abbr>** к подсказкам типов с помощью `Annotated`.
|
||||
|
||||
//// tab | Python 3.9+
|
||||
|
||||
В Python 3.9 `Annotated` входит в стандартную библиотеку, поэтому вы можете импортировать его из `typing`.
|
||||
|
||||
```Python hl_lines="1 4"
|
||||
{!> ../../docs_src/python_types/tutorial013_py39.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+
|
||||
|
||||
В версиях ниже Python 3.9 импортируйте `Annotated` из `typing_extensions`.
|
||||
|
||||
Он уже будет установлен вместе с **FastAPI**.
|
||||
|
||||
```Python hl_lines="1 4"
|
||||
{!> ../../docs_src/python_types/tutorial013.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
Сам Python ничего не делает с `Annotated`. А для редакторов кода и других инструментов тип по-прежнему `str`.
|
||||
|
||||
Но вы можете использовать это место в `Annotated`, чтобы передать **FastAPI** дополнительные метаданные о том, как вы хотите, чтобы ваше приложение себя вело.
|
||||
|
||||
Важно помнить, что **первый параметр типа**, который вы передаёте в `Annotated`, — это **фактический тип**. Всё остальное — просто метаданные для других инструментов.
|
||||
|
||||
Пока вам достаточно знать, что `Annotated` существует и это — стандартный Python. 😎
|
||||
|
||||
Позже вы увидите, насколько это **мощно**.
|
||||
|
||||
/// tip | Совет
|
||||
|
||||
Тот факт, что это **стандартный Python**, означает, что вы по-прежнему получите **лучший возможный разработческий опыт** в вашем редакторе кода, с инструментами для анализа и рефакторинга кода и т.д. ✨
|
||||
|
||||
А ещё ваш код будет очень совместим со множеством других инструментов и библиотек Python. 🚀
|
||||
|
||||
///
|
||||
|
||||
## Аннотации типов в **FastAPI** { #type-hints-in-fastapi }
|
||||
|
||||
**FastAPI** использует эти подсказки типов для выполнения нескольких задач.
|
||||
|
||||
С **FastAPI** вы объявляете параметры с подсказками типов и получаете:
|
||||
|
||||
* **Поддержку редактора кода**.
|
||||
* **Проверки типов**.
|
||||
|
||||
...и **FastAPI** использует эти же объявления для:
|
||||
|
||||
* **Определения требований**: из path-параметров, query-параметров, HTTP-заголовков, тел запросов, зависимостей и т.д.
|
||||
* **Преобразования данных**: из HTTP-запроса к требуемому типу.
|
||||
* **Валидации данных**: приходящих с каждого HTTP-запроса:
|
||||
* Генерации **автоматических ошибок**, возвращаемых клиенту, когда данные некорректны.
|
||||
* **Документирования** API с использованием OpenAPI:
|
||||
* что затем используется пользовательскими интерфейсами автоматической интерактивной документации.
|
||||
|
||||
Всё это может звучать абстрактно. Не волнуйтесь. Вы увидите всё это в действии в [Руководстве пользователя](tutorial/index.md){.internal-link target=_blank}.
|
||||
|
||||
Важно то, что, используя стандартные типы Python в одном месте (вместо добавления дополнительных классов, декораторов и т.д.), **FastAPI** сделает за вас большую часть работы.
|
||||
|
||||
/// info | Информация
|
||||
|
||||
Если вы уже прошли всё руководство и вернулись, чтобы узнать больше о типах, хорошим ресурсом будет <a href="https://mypy.readthedocs.io/en/latest/cheat_sheet_py3.html" class="external-link" target="_blank">«шпаргалка» от `mypy`</a>.
|
||||
|
||||
///
|
||||
|
||||
@@ -1,84 +1,84 @@
|
||||
# Фоновые задачи
|
||||
# Фоновые задачи { #background-tasks }
|
||||
|
||||
Вы можете создавать фоновые задачи, которые будут выполнятся *после* возвращения ответа сервером.
|
||||
Вы можете создавать фоновые задачи, которые будут выполняться после возврата ответа.
|
||||
|
||||
Это может быть полезно для функций, которые должны выполниться после получения запроса, но ожидание их выполнения необязательно для пользователя.
|
||||
Это полезно для операций, которые должны произойти после HTTP-запроса, но клиенту не обязательно ждать их завершения, чтобы получить ответ.
|
||||
|
||||
К примеру:
|
||||
Например:
|
||||
|
||||
* Отправка писем на почту после выполнения каких-либо действий:
|
||||
* Т.к. соединение с почтовым сервером и отправка письма идут достаточно "долго" (несколько секунд), вы можете отправить ответ пользователю, а отправку письма выполнить в фоне.
|
||||
* Уведомления по электронной почте, отправляемые после выполнения действия:
|
||||
* Так как подключение к почтовому серверу и отправка письма обычно «медленные» (несколько секунд), вы можете сразу вернуть ответ, а отправку уведомления выполнить в фоне.
|
||||
* Обработка данных:
|
||||
* К примеру, если вы получаете файл, который должен пройти через медленный процесс, вы можете отправить ответ "Accepted" (HTTP 202) и отправить работу с файлом в фон.
|
||||
* Например, если вы получаете файл, который должен пройти через медленный процесс, вы можете вернуть ответ «Accepted» (HTTP 202) и обработать файл в фоне.
|
||||
|
||||
## Использование класса `BackgroundTasks`
|
||||
## Использование `BackgroundTasks` { #using-backgroundtasks }
|
||||
|
||||
Сначала импортируйте `BackgroundTasks`, потом добавьте в функцию параметр с типом `BackgroundTasks`:
|
||||
Сначала импортируйте `BackgroundTasks` и объявите параметр в вашей функции‑обработчике пути с типом `BackgroundTasks`:
|
||||
|
||||
{* ../../docs_src/background_tasks/tutorial001.py hl[1,13] *}
|
||||
|
||||
**FastAPI** создаст объект класса `BackgroundTasks` для вас и запишет его в параметр.
|
||||
**FastAPI** создаст объект типа `BackgroundTasks` для вас и передаст его через этот параметр.
|
||||
|
||||
## Создание функции для фоновой задачи
|
||||
## Создание функции для фоновой задачи { #create-a-task-function }
|
||||
|
||||
Создайте функцию, которую хотите запустить в фоне.
|
||||
Создайте функцию, которую нужно запустить как фоновую задачу.
|
||||
|
||||
Это совершенно обычная функция, которая может принимать параметры.
|
||||
Это обычная функция, которая может принимать параметры.
|
||||
|
||||
Она может быть как асинхронной `async def`, так и обычной `def` функцией, **FastAPI** знает, как правильно ее выполнить.
|
||||
Это может быть как `async def`, так и обычная функция `def`, **FastAPI** знает, как корректно её выполнить.
|
||||
|
||||
В нашем примере фоновая задача будет вести запись в файл (симулируя отправку письма).
|
||||
В этом случае функция задачи будет записывать данные в файл (имитируя отправку письма).
|
||||
|
||||
Так как операция записи не использует `async` и `await`, мы определим ее как обычную `def`:
|
||||
Так как операция записи не использует `async` и `await`, мы определим функцию как обычную `def`:
|
||||
|
||||
{* ../../docs_src/background_tasks/tutorial001.py hl[6:9] *}
|
||||
|
||||
## Добавление фоновой задачи
|
||||
## Добавление фоновой задачи { #add-the-background-task }
|
||||
|
||||
Внутри функции вызовите метод `.add_task()` у объекта *background tasks* и передайте ему функцию, которую хотите выполнить в фоне:
|
||||
Внутри вашей функции‑обработчика пути передайте функцию задачи объекту фоновых задач методом `.add_task()`:
|
||||
|
||||
{* ../../docs_src/background_tasks/tutorial001.py hl[14] *}
|
||||
|
||||
`.add_task()` принимает следующие аргументы:
|
||||
|
||||
* Функцию, которая будет выполнена в фоне (`write_notification`). Обратите внимание, что передается объект функции, без скобок.
|
||||
* Любое упорядоченное количество аргументов, которые принимает функция (`email`).
|
||||
* Любое количество именованных аргументов, которые принимает функция (`message="some notification"`).
|
||||
* Функцию задачи, которую нужно выполнить в фоне (`write_notification`).
|
||||
* Последовательность позиционных аргументов, которые должны быть переданы функции задачи, в порядке (`email`).
|
||||
* Любые именованные аргументы, которые должны быть переданы функции задачи (`message="some notification"`).
|
||||
|
||||
## Встраивание зависимостей
|
||||
## Встраивание зависимостей { #dependency-injection }
|
||||
|
||||
Класс `BackgroundTasks` также работает с системой встраивания зависимостей, вы можете определить `BackgroundTasks` на разных уровнях: как параметр функции, как завимость, как подзависимость и так далее.
|
||||
Использование `BackgroundTasks` также работает с системой встраивания зависимостей, вы можете объявить параметр типа `BackgroundTasks` на нескольких уровнях: в функции‑обработчике пути, в зависимости (dependable), в подзависимости и т. д.
|
||||
|
||||
**FastAPI** знает, что нужно сделать в каждом случае и как переиспользовать тот же объект `BackgroundTasks`, так чтобы все фоновые задачи собрались и запустились вместе в фоне:
|
||||
**FastAPI** знает, что делать в каждом случае и как переиспользовать один и тот же объект, так чтобы все фоновые задачи были объединены и затем выполнены в фоне:
|
||||
|
||||
{* ../../docs_src/background_tasks/tutorial002_py310.py hl[11,13,20,23] *}
|
||||
{* ../../docs_src/background_tasks/tutorial002_an_py310.py hl[13,15,22,25] *}
|
||||
|
||||
В этом примере сообщения будут записаны в `log.txt` *после* того, как ответ сервера был отправлен.
|
||||
В этом примере сообщения будут записаны в файл `log.txt` после отправки ответа.
|
||||
|
||||
Если бы в запрос был передан query-параметр `q`, он бы первыми записался в `log.txt` фоновой задачей (потому что вызывается в зависимости `get_query`).
|
||||
Если в запросе была строка запроса (query), она будет записана в лог фоновой задачей.
|
||||
|
||||
После другая фоновая задача, которая была сгенерирована в функции, запишет сообщение из параметра `email`.
|
||||
Затем другая фоновая задача, созданная в функции‑обработчике пути, запишет сообщение, используя path‑параметр `email`.
|
||||
|
||||
## Технические детали
|
||||
## Технические детали { #technical-details }
|
||||
|
||||
Класс `BackgroundTasks` основан на <a href="https://www.starlette.io/background/" class="external-link" target="_blank">`starlette.background`</a>.
|
||||
Класс `BackgroundTasks` приходит напрямую из <a href="https://www.starlette.io/background/" class="external-link" target="_blank">`starlette.background`</a>.
|
||||
|
||||
Он интегрирован в FastAPI, так что вы можете импортировать его прямо из `fastapi` и избежать случайного импорта `BackgroundTask` (без `s` на конце) из `starlette.background`.
|
||||
Он импортируется/включается прямо в FastAPI, чтобы вы могли импортировать его из `fastapi` и избежать случайного импорта альтернативного `BackgroundTask` (без `s` на конце) из `starlette.background`.
|
||||
|
||||
При использовании `BackgroundTasks` (а не `BackgroundTask`), вам достаточно только определить параметр функции с типом `BackgroundTasks` и **FastAPI** сделает все за вас, также как при использовании объекта `Request`.
|
||||
Используя только `BackgroundTasks` (а не `BackgroundTask`), его можно применять как параметр функции‑обработчика пути, и **FastAPI** сделает остальное за вас, как при использовании объекта `Request` напрямую.
|
||||
|
||||
Вы все равно можете использовать `BackgroundTask` из `starlette` в FastAPI, но вам придется самостоятельно создавать объект фоновой задачи и вручную обработать `Response` внутри него.
|
||||
По‑прежнему можно использовать один `BackgroundTask` в FastAPI, но тогда вам нужно создать объект в своём коде и вернуть Starlette `Response`, включающий его.
|
||||
|
||||
Вы можете подробнее изучить его в <a href="https://www.starlette.io/background/" class="external-link" target="_blank">Официальной документации Starlette для BackgroundTasks</a>.
|
||||
Подробнее см. в <a href="https://www.starlette.io/background/" class="external-link" target="_blank">официальной документации Starlette по фоновым задачам</a>.
|
||||
|
||||
## Предостережение
|
||||
## Предостережение { #caveat }
|
||||
|
||||
Если вам нужно выполнить тяжелые вычисления в фоне, которым необязательно быть запущенными в одном процессе с приложением **FastAPI** (к примеру, вам не нужны обрабатываемые переменные или вы не хотите делиться памятью процесса и т.д.), вы можете использовать более серьезные инструменты, такие как <a href="https://docs.celeryproject.org" class="external-link" target="_blank">Celery</a>.
|
||||
Если вам нужно выполнять тяжелые вычисления в фоне, и при этом они не обязательно должны запускаться тем же процессом (например, вам не нужно делиться памятью, переменными и т. п.), вам могут подойти более мощные инструменты, такие как <a href="https://docs.celeryq.dev" class="external-link" target="_blank">Celery</a>.
|
||||
|
||||
Их тяжелее настраивать, также им нужен брокер сообщений наподобие RabbitMQ или Redis, но зато они позволяют вам запускать фоновые задачи в нескольких процессах и даже на нескольких серверах.
|
||||
Они обычно требуют более сложной конфигурации, менеджера очереди сообщений/заданий (например, RabbitMQ или Redis), но позволяют запускать фоновые задачи в нескольких процессах и, что особенно важно, на нескольких серверах.
|
||||
|
||||
Но если вам нужен доступ к общим переменным и объектам вашего **FastAPI** приложения или вам нужно выполнять простые фоновые задачи (наподобие отправки письма из примера) вы можете просто использовать `BackgroundTasks`.
|
||||
Но если вам нужен доступ к переменным и объектам из того же приложения **FastAPI**, или нужно выполнять небольшие фоновые задачи (например, отправку email‑уведомления), вы можете просто использовать `BackgroundTasks`.
|
||||
|
||||
## Резюме
|
||||
## Резюме { #recap }
|
||||
|
||||
Для создания фоновых задач вам необходимо импортировать `BackgroundTasks` и добавить его в функцию, как параметр с типом `BackgroundTasks`.
|
||||
Импортируйте и используйте `BackgroundTasks` с параметрами в функциях‑обработчиках пути и зависимостях, чтобы добавлять фоновые задачи.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Большие приложения, в которых много файлов
|
||||
# Большие приложения, в которых много файлов { #bigger-applications-multiple-files }
|
||||
|
||||
При построении приложения или веб-API нам редко удается поместить всё в один файл.
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
///
|
||||
|
||||
## Пример структуры приложения
|
||||
## Пример структуры приложения { #an-example-file-structure }
|
||||
|
||||
Давайте предположим, что наше приложение имеет следующую структуру:
|
||||
|
||||
@@ -71,7 +71,7 @@ from app.routers import items
|
||||
│ └── admin.py # суб-модуль "admin", напр.: import app.internal.admin
|
||||
```
|
||||
|
||||
## `APIRouter`
|
||||
## `APIRouter` { #apirouter }
|
||||
|
||||
Давайте предположим, что для работы с пользователями используется отдельный файл (суб-модуль) `/app/routers/users.py`.
|
||||
|
||||
@@ -81,8 +81,7 @@ from app.routers import items
|
||||
|
||||
С помощью `APIRouter` вы можете создать *операции пути* (*эндпоинты*) для данного модуля.
|
||||
|
||||
|
||||
### Импорт `APIRouter`
|
||||
### Импорт `APIRouter` { #import-apirouter }
|
||||
|
||||
Точно также, как и в случае с классом `FastAPI`, вам нужно импортировать и создать объект класса `APIRouter`.
|
||||
|
||||
@@ -90,7 +89,7 @@ from app.routers import items
|
||||
{!../../docs_src/bigger_applications/app/routers/users.py!}
|
||||
```
|
||||
|
||||
### Создание *эндпоинтов* с помощью `APIRouter`
|
||||
### Создание *эндпоинтов* с помощью `APIRouter` { #path-operations-with-apirouter }
|
||||
|
||||
В дальнейшем используйте `APIRouter` для объявления *эндпоинтов*, точно также, как вы используете класс `FastAPI`:
|
||||
|
||||
@@ -112,7 +111,7 @@ from app.routers import items
|
||||
|
||||
Мы собираемся подключить данный `APIRouter` к нашему основному приложению на `FastAPI`, но сначала давайте проверим зависимости и создадим ещё один модуль с `APIRouter`.
|
||||
|
||||
## Зависимости
|
||||
## Зависимости { #dependencies }
|
||||
|
||||
Нам понадобятся некоторые зависимости, которые мы будем использовать в разных местах нашего приложения.
|
||||
|
||||
@@ -154,11 +153,11 @@ from app.routers import items
|
||||
|
||||
Для простоты мы воспользовались неким воображаемым заголовоком.
|
||||
|
||||
В реальных случаях для получения наилучших результатов используйте интегрированные утилиты обеспечения безопасности [Security utilities](security/index.md){.internal-link target=_blank}.
|
||||
В реальных случаях для получения наилучших результатов используйте интегрированные [утилиты безопасности](security/index.md){.internal-link target=_blank}.
|
||||
|
||||
///
|
||||
|
||||
## Ещё один модуль с `APIRouter`
|
||||
## Ещё один модуль с `APIRouter` { #another-module-with-apirouter }
|
||||
|
||||
Давайте также предположим, что у вас есть *эндпоинты*, отвечающие за обработку "items", и они находятся в модуле `app/routers/items.py`.
|
||||
|
||||
@@ -203,7 +202,7 @@ async def read_item(item_id: str):
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Обратите внимание, что также, как и в случае с зависимостями в декораторах *эндпоинтов* ([dependencies in *path operation decorators*](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), никакого значения в *функцию эндпоинта* передано не будет.
|
||||
Обратите внимание, что также, как и в случае с зависимостями в декораторах *эндпоинтов* ([зависимости в декораторах операций пути](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), никакого значения в *функцию эндпоинта* передано не будет.
|
||||
|
||||
///
|
||||
|
||||
@@ -219,8 +218,8 @@ async def read_item(item_id: str):
|
||||
* Каждый из них будет включать предопределенные ответы `responses`.
|
||||
* Каждый *эндпоинт* будет иметь список зависимостей (`dependencies`), исполняемых перед вызовом *эндпоинта*.
|
||||
* Если вы определили зависимости в самой операции пути, **то она также будет выполнена**.
|
||||
* Сначала выполняются зависимости маршрутизатора, затем вызываются зависимости, определенные в декораторе *эндпоинта* ([`dependencies` in the decorator](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), и, наконец, обычные параметрические зависимости.
|
||||
* Вы также можете добавить зависимости безопасности с областями видимости (`scopes`) [`Security` dependencies with `scopes`](../advanced/security/oauth2-scopes.md){.internal-link target=_blank}.
|
||||
* Сначала выполняются зависимости маршрутизатора, затем вызываются [зависимости в декораторе](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, и, наконец, обычные параметрические зависимости.
|
||||
* Вы также можете добавить [зависимости `Security` с `scopes`](../advanced/security/oauth2-scopes.md){.internal-link target=_blank}.
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
@@ -234,7 +233,7 @@ async def read_item(item_id: str):
|
||||
|
||||
///
|
||||
|
||||
### Импорт зависимостей
|
||||
### Импорт зависимостей { #import-the-dependencies }
|
||||
|
||||
Наш код находится в модуле `app.routers.items` (файл `app/routers/items.py`).
|
||||
|
||||
@@ -246,7 +245,7 @@ async def read_item(item_id: str):
|
||||
{!../../docs_src/bigger_applications/app/routers/items.py!}
|
||||
```
|
||||
|
||||
#### Как работает относительный импорт?
|
||||
#### Как работает относительный импорт? { #how-relative-imports-work }
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
@@ -308,7 +307,7 @@ from ...dependencies import get_token_header
|
||||
|
||||
Теперь вы знаете, как работает импорт в Python, и сможете использовать относительное импортирование в своих собственных приложениях любого уровня сложности. 🤓
|
||||
|
||||
### Добавление пользовательских тегов (`tags`), ответов (`responses`) и зависимостей (`dependencies`)
|
||||
### Добавление пользовательских тегов (`tags`), ответов (`responses`) и зависимостей (`dependencies`) { #add-some-custom-tags-responses-and-dependencies }
|
||||
|
||||
Мы не будем добавлять префикс `/items` и список тегов `tags=["items"]` для каждого *эндпоинта*, т.к. мы уже их добавили с помощью `APIRouter`.
|
||||
|
||||
@@ -326,7 +325,7 @@ from ...dependencies import get_token_header
|
||||
|
||||
///
|
||||
|
||||
## Модуль main в `FastAPI`
|
||||
## Модуль main в `FastAPI` { #the-main-fastapi }
|
||||
|
||||
Теперь давайте посмотрим на модуль `app/main.py`.
|
||||
|
||||
@@ -336,17 +335,17 @@ from ...dependencies import get_token_header
|
||||
|
||||
И теперь, когда большая часть логики приложения разделена на отдельные модули, основной файл `app/main.py` будет достаточно простым.
|
||||
|
||||
### Импорт `FastAPI`
|
||||
### Импорт `FastAPI` { #import-fastapi }
|
||||
|
||||
Вы импортируете и создаете класс `FastAPI` как обычно.
|
||||
|
||||
Мы даже можем объявить глобальные зависимости [global dependencies](dependencies/global-dependencies.md){.internal-link target=_blank}, которые будут объединены с зависимостями для каждого отдельного маршрутизатора:
|
||||
Мы даже можем объявить [глобальные зависимости](dependencies/global-dependencies.md){.internal-link target=_blank}, которые будут объединены с зависимостями для каждого отдельного маршрутизатора:
|
||||
|
||||
```Python hl_lines="1 3 7" title="app/main.py"
|
||||
{!../../docs_src/bigger_applications/app/main.py!}
|
||||
```
|
||||
|
||||
### Импорт `APIRouter`
|
||||
### Импорт `APIRouter` { #import-the-apirouter }
|
||||
|
||||
Теперь мы импортируем другие суб-модули, содержащие `APIRouter`:
|
||||
|
||||
@@ -356,7 +355,7 @@ from ...dependencies import get_token_header
|
||||
|
||||
Так как файлы `app/routers/users.py` и `app/routers/items.py` являются суб-модулями одного и того же Python-пакета `app`, то мы сможем их импортировать, воспользовавшись операцией относительного импорта `.`.
|
||||
|
||||
### Как работает импорт?
|
||||
### Как работает импорт? { #how-the-importing-works }
|
||||
|
||||
Данная строка кода:
|
||||
|
||||
@@ -398,7 +397,7 @@ from app.routers import items, users
|
||||
|
||||
///
|
||||
|
||||
### Избегайте конфликтов имен
|
||||
### Избегайте конфликтов имен { #avoid-name-collisions }
|
||||
|
||||
Вместо того чтобы импортировать только переменную `router`, мы импортируем непосредственно суб-модуль `items`.
|
||||
|
||||
@@ -419,7 +418,7 @@ from .routers.users import router
|
||||
{!../../docs_src/bigger_applications/app/main.py!}
|
||||
```
|
||||
|
||||
### Подключение маршрутизаторов (`APIRouter`) для `users` и для `items`
|
||||
### Подключение маршрутизаторов (`APIRouter`) для `users` и для `items` { #include-the-apirouters-for-users-and-items }
|
||||
|
||||
Давайте подключим маршрутизаторы (`router`) из суб-модулей `users` и `items`:
|
||||
|
||||
@@ -457,7 +456,7 @@ from .routers.users import router
|
||||
|
||||
///
|
||||
|
||||
### Подключение `APIRouter` с пользовательскими префиксом (`prefix`), тегами (`tags`), ответами (`responses`), и зависимостями (`dependencies`)
|
||||
### Подключение `APIRouter` с пользовательскими префиксом (`prefix`), тегами (`tags`), ответами (`responses`), и зависимостями (`dependencies`) { #include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies }
|
||||
|
||||
Теперь давайте представим, что ваша организация передала вам файл `app/internal/admin.py`.
|
||||
|
||||
@@ -491,7 +490,7 @@ from .routers.users import router
|
||||
|
||||
Например, другие проекты, могут использовать тот же самый `APIRouter` с другими методами аутентификации.
|
||||
|
||||
### Подключение отдельного *эндпоинта*
|
||||
### Подключение отдельного *эндпоинта* { #include-a-path-operation }
|
||||
|
||||
Мы также можем добавить *эндпоинт* непосредственно в основное приложение `FastAPI`.
|
||||
|
||||
@@ -517,7 +516,7 @@ from .routers.users import router
|
||||
|
||||
///
|
||||
|
||||
## Проверка автоматической документации API
|
||||
## Проверка автоматической документации API { #check-the-automatic-api-docs }
|
||||
|
||||
Теперь запустите приложение:
|
||||
|
||||
@@ -537,7 +536,7 @@ $ fastapi dev app/main.py
|
||||
|
||||
<img src="/img/tutorial/bigger-applications/image01.png">
|
||||
|
||||
## Подключение существующего маршрута через новый префикс (`prefix`)
|
||||
## Подключение существующего маршрута через новый префикс (`prefix`) { #include-the-same-router-multiple-times-with-different-prefix }
|
||||
|
||||
Вы можете использовать `.include_router()` несколько раз с одним и тем же маршрутом, применив различные префиксы.
|
||||
|
||||
@@ -545,7 +544,7 @@ $ fastapi dev app/main.py
|
||||
|
||||
Это продвинутый способ, который вам может и не пригодится. Мы приводим его на случай, если вдруг вам это понадобится.
|
||||
|
||||
## Включение одного маршрутизатора (`APIRouter`) в другой
|
||||
## Включение одного маршрутизатора (`APIRouter`) в другой { #include-an-apirouter-in-another }
|
||||
|
||||
Точно так же, как вы включаете `APIRouter` в приложение `FastAPI`, вы можете включить `APIRouter` в другой `APIRouter`:
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# Body - Поля
|
||||
# Body - Поля { #body-fields }
|
||||
|
||||
Таким же способом, как вы объявляете дополнительную валидацию и метаданные в параметрах *функции обработки пути* с помощью функций `Query`, `Path` и `Body`, вы можете объявлять валидацию и метаданные внутри Pydantic моделей, используя функцию `Field` из Pydantic.
|
||||
|
||||
## Импорт `Field`
|
||||
## Импорт `Field` { #import-field }
|
||||
|
||||
Сначала вы должны импортировать его:
|
||||
|
||||
{* ../../docs_src/body_fields/tutorial001_py310.py hl[2] *}
|
||||
{* ../../docs_src/body_fields/tutorial001_an_py310.py hl[4] *}
|
||||
|
||||
/// warning | Внимание
|
||||
|
||||
@@ -14,11 +14,11 @@
|
||||
|
||||
///
|
||||
|
||||
## Объявление атрибутов модели
|
||||
## Объявление атрибутов модели { #declare-model-attributes }
|
||||
|
||||
Вы можете использовать функцию `Field` с атрибутами модели:
|
||||
|
||||
{* ../../docs_src/body_fields/tutorial001_py310.py hl[9:12] *}
|
||||
{* ../../docs_src/body_fields/tutorial001_an_py310.py hl[11:14] *}
|
||||
|
||||
Функция `Field` работает так же, как `Query`, `Path` и `Body`, у неё такие же параметры и т.д.
|
||||
|
||||
@@ -26,9 +26,9 @@
|
||||
|
||||
На самом деле, `Query`, `Path` и другие функции, которые вы увидите в дальнейшем, создают объекты подклассов общего класса `Param`, который сам по себе является подклассом `FieldInfo` из Pydantic.
|
||||
|
||||
И `Field` (из Pydantic), и `Body`, оба возвращают объекты подкласса `FieldInfo`.
|
||||
И `Field` (из Pydantic) также возвращает экземпляр `FieldInfo`.
|
||||
|
||||
У класса `Body` есть и другие подклассы, с которыми вы ознакомитесь позже.
|
||||
`Body` также напрямую возвращает объекты подкласса `FieldInfo`. И есть и другие, с которыми вы познакомитесь позже, которые являются подклассами класса `Body`.
|
||||
|
||||
Помните, что когда вы импортируете `Query`, `Path` и другое из `fastapi`, это фактически функции, которые возвращают специальные классы.
|
||||
|
||||
@@ -40,12 +40,11 @@
|
||||
|
||||
///
|
||||
|
||||
## Добавление дополнительной информации
|
||||
## Добавление дополнительной информации { #add-extra-information }
|
||||
|
||||
Вы можете объявлять дополнительную информацию в `Field`, `Query`, `Body` и т.п. Она будет включена в сгенерированную JSON схему.
|
||||
|
||||
Вы узнаете больше о добавлении дополнительной информации позже в документации, когда будете изучать, как задавать примеры принимаемых данных.
|
||||
|
||||
Вы узнаете больше о добавлении дополнительной информации позже в документации, когда будете изучать, как задавать примеры.
|
||||
|
||||
/// warning | Внимание
|
||||
|
||||
@@ -54,7 +53,7 @@
|
||||
|
||||
///
|
||||
|
||||
## Резюме
|
||||
## Резюме { #recap }
|
||||
|
||||
Вы можете использовать функцию `Field` из Pydantic, чтобы задавать дополнительную валидацию и метаданные для атрибутов модели.
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Body - Множество параметров
|
||||
# Body - Множество параметров { #body-multiple-parameters }
|
||||
|
||||
Теперь, когда мы увидели, как использовать `Path` и `Query` параметры, давайте рассмотрим более продвинутые примеры обьявления тела запроса.
|
||||
Теперь, когда мы увидели, как использовать `Path` и `Query` параметры, давайте рассмотрим более продвинутые примеры объявления тела запроса.
|
||||
|
||||
## Обьединение `Path`, `Query` и параметров тела запроса
|
||||
## Объединение `Path`, `Query` и параметров тела запроса { #mix-path-query-and-body-parameters }
|
||||
|
||||
Во-первых, конечно, вы можете объединять параметры `Path`, `Query` и объявления тела запроса в своих функциях обработки, **FastAPI** автоматически определит, что с ними нужно делать.
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
|
||||
///
|
||||
|
||||
## Несколько параметров тела запроса
|
||||
## Несколько параметров тела запроса { #multiple-body-parameters }
|
||||
|
||||
В предыдущем примере, *операции пути* ожидали тело запроса в формате JSON-тело с параметрами, соответствующими атрибутам `Item`, например:
|
||||
В предыдущем примере, *операции пути* ожидали тело запроса в формате JSON, с параметрами, соответствующими атрибутам `Item`, например:
|
||||
|
||||
```JSON
|
||||
{
|
||||
@@ -33,7 +33,7 @@
|
||||
|
||||
{* ../../docs_src/body_multiple_params/tutorial002_py310.py hl[20] *}
|
||||
|
||||
В этом случае **FastAPI** заметит, что в функции есть более одного параметра тела (два параметра, которые являются моделями Pydantic).
|
||||
В этом случае **FastAPI** заметит, что в функции есть более одного параметра тела (два параметра, которые являются Pydantic-моделями).
|
||||
|
||||
Таким образом, имена параметров будут использоваться в качестве ключей (имён полей) в теле запроса, и будет ожидаться запрос следующего формата:
|
||||
|
||||
@@ -54,21 +54,21 @@
|
||||
|
||||
/// note | Внимание
|
||||
|
||||
Обратите внимание, что хотя параметр `item` был объявлен таким же способом, как и раньше, теперь предпологается, что он находится внутри тела с ключом `item`.
|
||||
Обратите внимание, что хотя параметр `item` был объявлен таким же способом, как и раньше, теперь предполагается, что он находится внутри тела с ключом `item`.
|
||||
|
||||
///
|
||||
|
||||
**FastAPI** сделает автоматические преобразование из запроса, так что параметр `item` получит своё конкретное содержимое, и то же самое происходит с пользователем `user`.
|
||||
**FastAPI** сделает автоматическое преобразование из запроса, так что параметр `item` получит своё конкретное содержимое, и то же самое происходит с пользователем `user`.
|
||||
|
||||
Произойдёт проверка составных данных, и создание документации в схеме OpenAPI и автоматических документах.
|
||||
|
||||
## Отдельные значения в теле запроса
|
||||
## Отдельные значения в теле запроса { #singular-values-in-body }
|
||||
|
||||
Точно так же, как `Query` и `Path` используются для определения дополнительных данных для query и path параметров, **FastAPI** предоставляет аналогичный инструмент - `Body`.
|
||||
|
||||
Например, расширяя предыдущую модель, вы можете решить, что вам нужен еще один ключ `importance` в том же теле запроса, помимо параметров `item` и `user`.
|
||||
|
||||
Если вы объявите его без указания, какой именно объект (Path, Query, Body и .т.п.) ожидаете, то, поскольку это является простым типом данных, **FastAPI** будет считать, что это query-параметр.
|
||||
Если вы объявите его без указания, какой именно объект (Path, Query, Body и т.п.) ожидаете, то, поскольку это является простым типом данных, **FastAPI** будет считать, что это query-параметр.
|
||||
|
||||
Но вы можете указать **FastAPI** обрабатывать его, как ещё один ключ тела запроса, используя `Body`:
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
|
||||
И всё будет работать так же - преобразование типов данных, валидация, документирование и т.д.
|
||||
|
||||
## Множество body и query параметров
|
||||
## Множество body и query параметров { #multiple-body-params-and-query }
|
||||
|
||||
Конечно, вы также можете объявлять query-параметры в любое время, дополнительно к любым body-параметрам.
|
||||
|
||||
@@ -112,7 +112,7 @@ q: str | None = None
|
||||
|
||||
Например:
|
||||
|
||||
{* ../../docs_src/body_multiple_params/tutorial004_an_py310.py hl[27] *}
|
||||
{* ../../docs_src/body_multiple_params/tutorial004_an_py310.py hl[28] *}
|
||||
|
||||
/// info | Информация
|
||||
|
||||
@@ -120,9 +120,9 @@ q: str | None = None
|
||||
|
||||
///
|
||||
|
||||
## Добавление одного body-параметра
|
||||
## Вложить один body-параметр { #embed-a-single-body-parameter }
|
||||
|
||||
Предположим, у вас есть только один body-параметр `item` из Pydantic модели `Item`.
|
||||
Предположим, у вас есть только один body-параметр `item` из Pydantic-модели `Item`.
|
||||
|
||||
По умолчанию, **FastAPI** ожидает получить тело запроса напрямую.
|
||||
|
||||
@@ -160,9 +160,9 @@ item: Item = Body(embed=True)
|
||||
}
|
||||
```
|
||||
|
||||
## Резюме
|
||||
## Резюме { #recap }
|
||||
|
||||
Вы можете добавлять несколько body-параметров вашей *функции операции пути*, несмотря даже на то, что запрос может содержать только одно тело.
|
||||
Вы можете добавлять несколько body-параметров вашей *функции-обработчика пути*, несмотря даже на то, что запрос может содержать только одно тело.
|
||||
|
||||
Но **FastAPI** справится с этим, предоставит правильные данные в вашей функции, а также сделает валидацию и документацию правильной схемы *операции пути*.
|
||||
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
# Body - Вложенные модели
|
||||
# Body - Вложенные модели { #body-nested-models }
|
||||
|
||||
С помощью **FastAPI**, вы можете определять, валидировать, документировать и использовать модели произвольной вложенности (благодаря библиотеке Pydantic).
|
||||
С помощью **FastAPI** вы можете определять, валидировать, документировать и использовать модели произвольной глубины вложенности (благодаря Pydantic).
|
||||
|
||||
## Определение полей содержащих списки
|
||||
## Поля-списки { #list-fields }
|
||||
|
||||
Вы можете определять атрибут как подтип. Например, тип `list` в Python:
|
||||
Вы можете определить атрибут как подтип. Например, Python-тип `list`:
|
||||
|
||||
{* ../../docs_src/body_nested_models/tutorial001_py310.py hl[12] *}
|
||||
|
||||
Это приведёт к тому, что обьект `tags` преобразуется в список, несмотря на то что тип его элементов не объявлен.
|
||||
Это приведёт к тому, что `tags` будет списком, несмотря на то, что тип его элементов не объявлен.
|
||||
|
||||
## Определение полей содержащих список с определением типов его элементов
|
||||
## Поля-списки с параметром типа { #list-fields-with-type-parameter }
|
||||
|
||||
Однако в Python есть способ объявления списков с указанием типов для вложенных элементов:
|
||||
В Python есть специальный способ объявлять списки с внутренними типами, или «параметрами типа»:
|
||||
|
||||
### Импортируйте `List` из модуля typing
|
||||
### Импортируйте `List` из модуля typing { #import-typings-list }
|
||||
|
||||
В Python 3.9 и выше вы можете использовать стандартный тип `list` для объявления аннотаций типов, как мы увидим ниже. 💡
|
||||
|
||||
Но в версиях Python до 3.9 (начиная с 3.6) сначала вам необходимо импортировать `List` из стандартного модуля `typing` в Python:
|
||||
Но в версиях Python до 3.9 (начиная с 3.6) сначала вам необходимо импортировать `List` из стандартного модуля `typing`:
|
||||
|
||||
{* ../../docs_src/body_nested_models/tutorial002.py hl[1] *}
|
||||
|
||||
### Объявление `list` с указанием типов для вложенных элементов
|
||||
### Объявите `list` с параметром типа { #declare-a-list-with-a-type-parameter }
|
||||
|
||||
Объявление типов для элементов (внутренних типов) вложенных в такие типы как `list`, `dict`, `tuple`:
|
||||
Для объявления типов, у которых есть параметры типа (внутренние типы), таких как `list`, `dict`, `tuple`:
|
||||
|
||||
* Если у вас Python версии ниже чем 3.9, импортируйте их аналог из модуля `typing`
|
||||
* Передайте внутренний(ие) тип(ы) как "параметры типа", используя квадратные скобки: `[` и `]`
|
||||
* Если у вас Python версии ниже 3.9, импортируйте их аналоги из модуля `typing`
|
||||
* Передайте внутренний(ие) тип(ы) как «параметры типа», используя квадратные скобки: `[` и `]`
|
||||
|
||||
В Python версии 3.9 это будет выглядеть так:
|
||||
В Python 3.9 это будет:
|
||||
|
||||
```Python
|
||||
my_list: list[str]
|
||||
```
|
||||
|
||||
В версиях Python до 3.9 это будет выглядеть так:
|
||||
В версиях Python до 3.9 это будет:
|
||||
|
||||
```Python
|
||||
from typing import List
|
||||
@@ -47,17 +47,17 @@ my_list: List[str]
|
||||
|
||||
Используйте этот же стандартный синтаксис для атрибутов модели с внутренними типами.
|
||||
|
||||
Таким образом, в нашем примере мы можем явно указать тип данных для поля `tags` как "список строк":
|
||||
Таким образом, в нашем примере мы можем явно указать тип данных для поля `tags` как «список строк»:
|
||||
|
||||
{* ../../docs_src/body_nested_models/tutorial002_py310.py hl[12] *}
|
||||
|
||||
## Типы множеств
|
||||
## Типы множеств { #set-types }
|
||||
|
||||
Но затем мы подумали и поняли, что теги не должны повторяться и, вероятно, они должны быть уникальными строками.
|
||||
Но затем мы подумали и поняли, что теги не должны повторяться, вероятно, это должны быть уникальные строки.
|
||||
|
||||
И в Python есть специальный тип данных для множеств уникальных элементов - `set`.
|
||||
И в Python есть специальный тип данных для множеств уникальных элементов — `set`.
|
||||
|
||||
Тогда мы можем обьявить поле `tags` как множество строк:
|
||||
Тогда мы можем объявить поле `tags` как множество строк:
|
||||
|
||||
{* ../../docs_src/body_nested_models/tutorial003_py310.py hl[12] *}
|
||||
|
||||
@@ -67,23 +67,23 @@ my_list: List[str]
|
||||
|
||||
И они также будут соответствующим образом аннотированы / задокументированы.
|
||||
|
||||
## Вложенные Модели
|
||||
## Вложенные модели { #nested-models }
|
||||
|
||||
У каждого атрибута Pydantic-модели есть тип.
|
||||
|
||||
Но этот тип может сам быть другой моделью Pydantic.
|
||||
Но этот тип сам может быть другой моделью Pydantic.
|
||||
|
||||
Таким образом вы можете объявлять глубоко вложенные JSON "объекты" с определёнными именами атрибутов, типами и валидацией.
|
||||
Таким образом, вы можете объявлять глубоко вложенные JSON «объекты» с определёнными именами атрибутов, типами и валидацией.
|
||||
|
||||
Всё это может быть произвольно вложенным.
|
||||
|
||||
### Определение подмодели
|
||||
### Определение подмодели { #define-a-submodel }
|
||||
|
||||
Например, мы можем определить модель `Image`:
|
||||
|
||||
{* ../../docs_src/body_nested_models/tutorial004_py310.py hl[7:9] *}
|
||||
|
||||
### Использование вложенной модели в качестве типа
|
||||
### Использование подмодели как типа { #use-the-submodel-as-a-type }
|
||||
|
||||
Также мы можем использовать эту модель как тип атрибута:
|
||||
|
||||
@@ -107,30 +107,30 @@ my_list: List[str]
|
||||
|
||||
Ещё раз: сделав такое объявление, с помощью **FastAPI** вы получите:
|
||||
|
||||
* Поддержку редакторов IDE (автодополнение и т.д), даже для вложенных моделей
|
||||
* Поддержку редактора кода (автозавершение и т. д.), даже для вложенных моделей
|
||||
* Преобразование данных
|
||||
* Валидацию данных
|
||||
* Автоматическую документацию
|
||||
|
||||
## Особые типы и валидация
|
||||
## Особые типы и валидация { #special-types-and-validation }
|
||||
|
||||
Помимо обычных простых типов, таких как `str`, `int`, `float`, и т.д. Вы можете использовать более сложные базовые типы, которые наследуются от типа `str`.
|
||||
Помимо обычных простых типов, таких как `str`, `int`, `float` и т.д., вы можете использовать более сложные простые типы, которые наследуются от `str`.
|
||||
|
||||
Чтобы увидеть все варианты, которые у вас есть, ознакомьтесь с документацией <a href="https://docs.pydantic.dev/latest/concepts/types/" class="external-link" target="_blank">по необычным типам Pydantic</a>. Вы увидите некоторые примеры в следующей главе.
|
||||
Чтобы увидеть все варианты, которые у вас есть, ознакомьтесь с <a href="https://docs.pydantic.dev/latest/concepts/types/" class="external-link" target="_blank">обзором типов Pydantic</a>. Вы увидите некоторые примеры в следующей главе.
|
||||
|
||||
Например, так как в модели `Image` у нас есть поле `url`, то мы можем объявить его как тип `HttpUrl` из модуля Pydantic вместо типа `str`:
|
||||
Например, так как в модели `Image` у нас есть поле `url`, то мы можем объявить его как тип `HttpUrl` из Pydantic вместо типа `str`:
|
||||
|
||||
{* ../../docs_src/body_nested_models/tutorial005_py310.py hl[2,8] *}
|
||||
|
||||
Строка будет проверена на соответствие допустимому URL-адресу и задокументирована в JSON схему / OpenAPI.
|
||||
Строка будет проверена на соответствие допустимому URL-адресу и задокументирована в JSON Schema / OpenAPI как таковая.
|
||||
|
||||
## Атрибуты, содержащие списки подмоделей
|
||||
## Атрибуты, содержащие списки подмоделей { #attributes-with-lists-of-submodels }
|
||||
|
||||
Вы также можете использовать модели Pydantic в качестве типов вложенных в `list`, `set` и т.д:
|
||||
Вы также можете использовать модели Pydantic в качестве подтипов для `list`, `set` и т.д.:
|
||||
|
||||
{* ../../docs_src/body_nested_models/tutorial006_py310.py hl[18] *}
|
||||
|
||||
Такая реализация будет ожидать (конвертировать, валидировать, документировать и т.д) JSON-содержимое в следующем формате:
|
||||
Такая реализация будет ожидать (конвертировать, валидировать, документировать и т.д.) JSON-содержимое в следующем формате:
|
||||
|
||||
```JSON hl_lines="11"
|
||||
{
|
||||
@@ -162,7 +162,7 @@ my_list: List[str]
|
||||
|
||||
///
|
||||
|
||||
## Глубоко вложенные модели
|
||||
## Глубоко вложенные модели { #deeply-nested-models }
|
||||
|
||||
Вы можете определять модели с произвольным уровнем вложенности:
|
||||
|
||||
@@ -174,15 +174,15 @@ my_list: List[str]
|
||||
|
||||
///
|
||||
|
||||
## Тела с чистыми списками элементов
|
||||
## Тела с чистыми списками элементов { #bodies-of-pure-lists }
|
||||
|
||||
Если верхний уровень значения тела JSON-объекта представляет собой JSON `array` (в Python - `list`), вы можете объявить тип в параметре функции, так же, как в моделях Pydantic:
|
||||
Если верхний уровень значения тела JSON-объекта представляет собой JSON `array` (в Python — `list`), вы можете объявить тип в параметре функции, так же как в моделях Pydantic:
|
||||
|
||||
```Python
|
||||
images: List[Image]
|
||||
```
|
||||
|
||||
в Python 3.9 и выше:
|
||||
или в Python 3.9 и выше:
|
||||
|
||||
```Python
|
||||
images: list[Image]
|
||||
@@ -192,33 +192,33 @@ images: list[Image]
|
||||
|
||||
{* ../../docs_src/body_nested_models/tutorial008_py39.py hl[13] *}
|
||||
|
||||
## Универсальная поддержка редактора
|
||||
## Поддержка редактора кода везде { #editor-support-everywhere }
|
||||
|
||||
И вы получаете поддержку редактора везде.
|
||||
И вы получаете поддержку редактора кода везде.
|
||||
|
||||
Даже для элементов внутри списков:
|
||||
|
||||
<img src="/img/tutorial/body-nested-models/image01.png">
|
||||
|
||||
Вы не могли бы получить такую поддержку редактора, если бы работали напрямую с `dict`, а не с моделями Pydantic.
|
||||
Вы не могли бы получить такую поддержку редактора кода, если бы работали напрямую с `dict`, а не с моделями Pydantic.
|
||||
|
||||
Но вы также не должны беспокоиться об этом, входящие словари автоматически конвертируются, а ваш вывод также автоматически преобразуется в формат JSON.
|
||||
|
||||
## Тела запросов с произвольными словарями (`dict` )
|
||||
## Тела запросов с произвольными словарями (`dict`) { #bodies-of-arbitrary-dicts }
|
||||
|
||||
Вы также можете объявить тело запроса как `dict` с ключами определенного типа и значениями другого типа данных.
|
||||
Вы также можете объявить тело запроса как `dict` с ключами определённого типа и значениями другого типа.
|
||||
|
||||
Без необходимости знать заранее, какие значения являются допустимыми для имён полей/атрибутов (как это было бы в случае с моделями Pydantic).
|
||||
|
||||
Это было бы полезно, если вы хотите получить ключи, которые вы еще не знаете.
|
||||
Это было бы полезно, если вы хотите получить ключи, которые вы ещё не знаете.
|
||||
|
||||
---
|
||||
|
||||
Другой полезный случай - когда вы хотите чтобы ключи были другого типа данных, например, `int`.
|
||||
Другой полезный случай — когда вы хотите, чтобы ключи были другого типа данных, например, `int`.
|
||||
|
||||
Именно это мы сейчас и увидим здесь.
|
||||
|
||||
В этом случае вы принимаете `dict`, пока у него есть ключи типа `int` со значениями типа `float`:
|
||||
В этом случае вы принимаете любой `dict`, пока у него есть ключи типа `int` со значениями типа `float`:
|
||||
|
||||
{* ../../docs_src/body_nested_models/tutorial009_py39.py hl[7] *}
|
||||
|
||||
@@ -228,19 +228,19 @@ images: list[Image]
|
||||
|
||||
Но Pydantic обеспечивает автоматическое преобразование данных.
|
||||
|
||||
Это значит, что даже если пользователи вашего API могут отправлять только строки в качестве ключей, при условии, что эти строки содержат целые числа, Pydantic автоматический преобразует и валидирует эти данные.
|
||||
Это значит, что даже если клиенты вашего API могут отправлять только строки в качестве ключей, при условии, что эти строки содержат целые числа, Pydantic автоматически преобразует и валидирует эти данные.
|
||||
|
||||
А `dict`, с именем `weights`, который вы получите в качестве ответа Pydantic, действительно будет иметь ключи типа `int` и значения типа `float`.
|
||||
А `dict`, который вы получите как `weights`, действительно будет иметь ключи типа `int` и значения типа `float`.
|
||||
|
||||
///
|
||||
|
||||
## Резюме
|
||||
## Резюме { #recap }
|
||||
|
||||
С помощью **FastAPI** вы получаете максимальную гибкость, предоставляемую моделями Pydantic, сохраняя при этом простоту, краткость и элегантность вашего кода.
|
||||
|
||||
И дополнительно вы получаете:
|
||||
|
||||
* Поддержку редактора (автодополнение доступно везде!)
|
||||
* Поддержку редактора кода (автозавершение доступно везде!)
|
||||
* Преобразование данных (также известно как парсинг / сериализация)
|
||||
* Валидацию данных
|
||||
* Документацию схемы данных
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
# Body - Обновления
|
||||
# Body - Обновления { #body-updates }
|
||||
|
||||
## Полное обновление с помощью `PUT`
|
||||
## Обновление с заменой при помощи `PUT` { #update-replacing-with-put }
|
||||
|
||||
Для полного обновления элемента можно воспользоваться операцией <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT" class="external-link" target="_blank">HTTP `PUT`</a>.
|
||||
|
||||
Вы можете использовать функцию `jsonable_encoder` для преобразования входных данных в JSON, так как нередки случаи, когда работать можно только с простыми типами данных (например, для хранения в NoSQL-базе данных).
|
||||
Вы можете использовать `jsonable_encoder`, чтобы преобразовать входные данные в данные, которые можно сохранить как JSON (например, в NoSQL-базе данных). Например, преобразование `datetime` в `str`.
|
||||
|
||||
{* ../../docs_src/body_updates/tutorial001_py310.py hl[28:33] *}
|
||||
|
||||
`PUT` используется для получения данных, которые должны полностью заменить существующие данные.
|
||||
|
||||
### Предупреждение о замене
|
||||
### Предупреждение о замене { #warning-about-replacing }
|
||||
|
||||
Это означает, что если вы хотите обновить элемент `bar`, используя `PUT` с телом, содержащим:
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
И данные будут сохранены с этим "новым" `tax`, равным `10,5`.
|
||||
|
||||
## Частичное обновление с помощью `PATCH`
|
||||
## Частичное обновление с помощью `PATCH` { #partial-updates-with-patch }
|
||||
|
||||
Также можно использовать <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH" class="external-link" target="_blank">HTTP `PATCH`</a> операцию для *частичного* обновления данных.
|
||||
|
||||
@@ -44,25 +44,41 @@
|
||||
|
||||
///
|
||||
|
||||
### Использование параметра `exclude_unset` в Pydantic
|
||||
### Использование параметра `exclude_unset` в Pydantic { #using-pydantics-exclude-unset-parameter }
|
||||
|
||||
Если необходимо выполнить частичное обновление, то очень полезно использовать параметр `exclude_unset` в методе `.dict()` модели Pydantic.
|
||||
Если необходимо выполнить частичное обновление, то очень полезно использовать параметр `exclude_unset` в методе `.model_dump()` модели Pydantic.
|
||||
|
||||
Например, `item.dict(exclude_unset=True)`.
|
||||
Например, `item.model_dump(exclude_unset=True)`.
|
||||
|
||||
/// info | Информация
|
||||
|
||||
В Pydantic v1 метод назывался `.dict()`, в Pydantic v2 он помечен как устаревший (но все еще поддерживается) и переименован в `.model_dump()`.
|
||||
|
||||
Примеры здесь используют `.dict()` для совместимости с Pydantic v1, но если вы можете использовать Pydantic v2, лучше используйте `.model_dump()`.
|
||||
|
||||
///
|
||||
|
||||
В результате будет сгенерирован словарь, содержащий только те данные, которые были заданы при создании модели `item`, без учета значений по умолчанию. Затем вы можете использовать это для создания словаря только с теми данными, которые были установлены (отправлены в запросе), опуская значения по умолчанию:
|
||||
|
||||
{* ../../docs_src/body_updates/tutorial002_py310.py hl[32] *}
|
||||
|
||||
### Использование параметра `update` в Pydantic
|
||||
### Использование параметра `update` в Pydantic { #using-pydantics-update-parameter }
|
||||
|
||||
Теперь можно создать копию существующей модели, используя `.copy()`, и передать параметр `update` с `dict`, содержащим данные для обновления.
|
||||
Теперь можно создать копию существующей модели, используя `.model_copy()`, и передать параметр `update` с `dict`, содержащим данные для обновления.
|
||||
|
||||
Например, `stored_item_model.copy(update=update_data)`:
|
||||
/// info | Информация
|
||||
|
||||
В Pydantic v1 метод назывался `.copy()`, в Pydantic v2 он помечен как устаревший (но все еще поддерживается) и переименован в `.model_copy()`.
|
||||
|
||||
Примеры здесь используют `.copy()` для совместимости с Pydantic v1, но если вы можете использовать Pydantic v2, лучше используйте `.model_copy()`.
|
||||
|
||||
///
|
||||
|
||||
Например, `stored_item_model.model_copy(update=update_data)`:
|
||||
|
||||
{* ../../docs_src/body_updates/tutorial002_py310.py hl[33] *}
|
||||
|
||||
### Кратко о частичном обновлении
|
||||
### Кратко о частичном обновлении { #partial-updates-recap }
|
||||
|
||||
В целом, для применения частичных обновлений необходимо:
|
||||
|
||||
@@ -73,7 +89,7 @@
|
||||
* Таким образом, можно обновлять только те значения, которые действительно установлены пользователем, вместо того чтобы переопределять значения, уже сохраненные в модели по умолчанию.
|
||||
* Создать копию хранимой модели, обновив ее атрибуты полученными частичными обновлениями (с помощью параметра `update`).
|
||||
* Преобразовать скопированную модель в то, что может быть сохранено в вашей БД (например, с помощью `jsonable_encoder`).
|
||||
* Это сравнимо с повторным использованием метода модели `.dict()`, но при этом происходит проверка (и преобразование) значений в типы данных, которые могут быть преобразованы в JSON, например, `datetime` в `str`.
|
||||
* Это сравнимо с повторным использованием метода модели `.model_dump()`, но при этом происходит проверка (и преобразование) значений в типы данных, которые могут быть преобразованы в JSON, например, `datetime` в `str`.
|
||||
* Сохранить данные в своей БД.
|
||||
* Вернуть обновленную модель.
|
||||
|
||||
|
||||
@@ -1,40 +1,40 @@
|
||||
# Тело запроса
|
||||
# Тело запроса { #request-body }
|
||||
|
||||
Когда вам необходимо отправить данные из клиента (допустим, браузера) в ваш API, вы отправляете их как **тело запроса**.
|
||||
Когда вам необходимо отправить данные из клиента (например, браузера) в ваш API, вы отправляете их как **тело запроса**.
|
||||
|
||||
Тело **запроса** --- это данные, отправляемые клиентом в ваш API. Тело **ответа** --- это данные, которые ваш API отправляет клиенту.
|
||||
Тело **запроса** — это данные, отправляемые клиентом в ваш API. Тело **ответа** — это данные, которые ваш API отправляет клиенту.
|
||||
|
||||
Ваш API почти всегда отправляет тело **ответа**. Но клиентам не обязательно всегда отправлять тело **запроса**.
|
||||
Ваш API почти всегда должен отправлять тело **ответа**. Но клиентам не обязательно всегда отправлять **тело запроса**: иногда они запрашивают только путь, возможно с некоторыми параметрами запроса, но без тела.
|
||||
|
||||
Чтобы объявить тело **запроса**, необходимо использовать модели <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a>, со всей их мощью и преимуществами.
|
||||
Чтобы объявить тело **запроса**, используйте модели <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a>, со всей их мощью и преимуществами.
|
||||
|
||||
/// info | Информация
|
||||
|
||||
Чтобы отправить данные, необходимо использовать один из методов: `POST` (обычно), `PUT`, `DELETE` или `PATCH`.
|
||||
Чтобы отправить данные, используйте один из методов: `POST` (чаще всего), `PUT`, `DELETE` или `PATCH`.
|
||||
|
||||
Отправка тела с запросом `GET` имеет неопределенное поведение в спецификациях, тем не менее, оно поддерживается FastAPI только для очень сложных/экстремальных случаев использования.
|
||||
Отправка тела с запросом `GET` имеет неопределённое поведение в спецификациях, тем не менее это поддерживается FastAPI, но только для очень сложных/крайних случаев использования.
|
||||
|
||||
Поскольку это не рекомендуется, интерактивная документация со Swagger UI не будет отображать информацию для тела при использовании метода GET, а промежуточные прокси-серверы могут не поддерживать такой вариант запроса.
|
||||
Поскольку это не рекомендуется, интерактивная документация со Swagger UI не будет отображать информацию для тела при использовании `GET`, а промежуточные прокси-серверы могут не поддерживать такой вариант запроса.
|
||||
|
||||
///
|
||||
|
||||
## Импортирование `BaseModel` из Pydantic
|
||||
## Импортируйте `BaseModel` из Pydantic { #import-pydantics-basemodel }
|
||||
|
||||
Первое, что вам необходимо сделать, это импортировать `BaseModel` из пакета `pydantic`:
|
||||
Первое, что нужно сделать, — импортировать `BaseModel` из пакета `pydantic`:
|
||||
|
||||
{* ../../docs_src/body/tutorial001.py hl[4] *}
|
||||
{* ../../docs_src/body/tutorial001_py310.py hl[2] *}
|
||||
|
||||
## Создание вашей собственной модели
|
||||
## Создайте модель данных { #create-your-data-model }
|
||||
|
||||
После этого вы описываете вашу модель данных как класс, наследующий от `BaseModel`.
|
||||
Затем опишите свою модель данных как класс, наследующийся от `BaseModel`.
|
||||
|
||||
Используйте аннотации типов Python для всех атрибутов:
|
||||
Используйте стандартные типы Python для всех атрибутов:
|
||||
|
||||
{* ../../docs_src/body/tutorial001.py hl[7:11] *}
|
||||
{* ../../docs_src/body/tutorial001_py310.py hl[5:9] *}
|
||||
|
||||
Также как и при описании параметров запроса, когда атрибут модели имеет значение по умолчанию, он является необязательным. Иначе он обязателен. Используйте `None`, чтобы сделать его необязательным без использования конкретных значений по умолчанию.
|
||||
Так же, как при объявлении параметров запроса: когда атрибут модели имеет значение по умолчанию, он не обязателен. Иначе он обязателен. Используйте `None`, чтобы сделать его просто необязательным.
|
||||
|
||||
Например, модель выше описывает вот такой JSON "объект" (или словарь Python):
|
||||
Например, модель выше описывает такой JSON "объект" (или Python `dict`):
|
||||
|
||||
```JSON
|
||||
{
|
||||
@@ -45,7 +45,7 @@
|
||||
}
|
||||
```
|
||||
|
||||
...поскольку `description` и `tax` являются необязательными (с `None` в качестве значения по умолчанию), вот такой JSON "объект" также подходит:
|
||||
...так как `description` и `tax` являются необязательными (со значением по умолчанию `None`), такой JSON "объект" тоже будет корректным:
|
||||
|
||||
```JSON
|
||||
{
|
||||
@@ -54,109 +54,119 @@
|
||||
}
|
||||
```
|
||||
|
||||
## Объявление как параметра функции
|
||||
## Объявите её как параметр { #declare-it-as-a-parameter }
|
||||
|
||||
Чтобы добавить параметр к вашему *обработчику*, объявите его также, как вы объявляли параметры пути или параметры запроса:
|
||||
Чтобы добавить её в вашу *операцию пути*, объявите её так же, как вы объявляли параметры пути и параметры запроса:
|
||||
|
||||
{* ../../docs_src/body/tutorial001.py hl[18] *}
|
||||
{* ../../docs_src/body/tutorial001_py310.py hl[16] *}
|
||||
|
||||
...и укажите созданную модель в качестве типа параметра, `Item`.
|
||||
...и укажите тип параметра как созданную вами модель, `Item`.
|
||||
|
||||
## Результаты
|
||||
## Результаты { #results }
|
||||
|
||||
Всего лишь с помощью аннотации типов Python, **FastAPI**:
|
||||
Всего лишь с этой аннотацией типов Python **FastAPI**:
|
||||
|
||||
* Читает тело запроса как JSON.
|
||||
* Приводит к соответствующим типам (если есть необходимость).
|
||||
* Проверяет корректность данных.
|
||||
* Если данные некорректны, будет возращена читаемая и понятная ошибка, показывающая что именно и в каком месте некорректно в данных.
|
||||
* Складывает полученные данные в параметр `item`.
|
||||
* Поскольку внутри функции вы объявили его с типом `Item`, то теперь у вас есть поддержка со стороны редактора (автодополнение и т.п.) для всех атрибутов и их типов.
|
||||
* Генерирует декларативное описание модели в виде <a href="https://json-schema.org" class="external-link" target="_blank">JSON Schema</a>, так что вы можете его использовать где угодно, если это имеет значение для вашего проекта.
|
||||
* Эти схемы являются частью сгенерированной схемы OpenAPI и используются для автоматического документирования <abbr title="Пользовательских интерфейсов (User Interfaces)">UI</abbr>.
|
||||
* Считает тело запроса как JSON.
|
||||
* Приведёт данные к соответствующим типам (если потребуется).
|
||||
* Проведёт валидацию данных.
|
||||
* Если данные некорректны, вернёт понятную и наглядную ошибку, указывающую, где именно и что было некорректно.
|
||||
* Передаст полученные данные в параметр `item`.
|
||||
* Поскольку внутри функции вы объявили его с типом `Item`, у вас будет поддержка со стороны редактора кода (автозавершение и т. п.) для всех атрибутов и их типов.
|
||||
* Сгенерирует определения <a href="https://json-schema.org" class="external-link" target="_blank">JSON Schema</a> для вашей модели; вы можете использовать их и в других местах, если это имеет смысл для вашего проекта.
|
||||
* Эти схемы будут частью сгенерированной схемы OpenAPI и будут использоваться автоматической документацией <abbr title="User Interfaces – Пользовательские интерфейсы">UIs</abbr>.
|
||||
|
||||
## Автоматическое документирование
|
||||
## Автоматическая документация { #automatic-docs }
|
||||
|
||||
Схема JSON ваших моделей будет частью сгенерированной схемы OpenAPI и будет отображена в интерактивной документации API:
|
||||
JSON Schema ваших моделей будет частью сгенерированной схемы OpenAPI и будет отображаться в интерактивной документации API:
|
||||
|
||||
<img src="/img/tutorial/body/image01.png">
|
||||
|
||||
Также она будет указана в документации по API внутри каждой *операции пути*, в которой используются:
|
||||
А также они будут использоваться в документации API внутри каждой *операции пути*, где это требуется:
|
||||
|
||||
<img src="/img/tutorial/body/image02.png">
|
||||
|
||||
## Поддержка редактора
|
||||
## Поддержка редактора кода { #editor-support }
|
||||
|
||||
В вашем редакторе внутри вашей функции у вас будут подсказки по типам и автодополнение (это не будет работать, если вы получаете словарь вместо модели Pydantic):
|
||||
В вашем редакторе кода внутри функции вы получите подсказки по типам и автозавершение повсюду (этого бы не было, если бы вы получали `dict` вместо модели Pydantic):
|
||||
|
||||
<img src="/img/tutorial/body/image03.png">
|
||||
|
||||
Также вы будете получать ошибки в случае несоответствия типов:
|
||||
Также вы получите проверку ошибок при некорректных операциях с типами:
|
||||
|
||||
<img src="/img/tutorial/body/image04.png">
|
||||
|
||||
Это не случайно, весь фреймворк построен вокруг такого дизайна.
|
||||
Это не случайность — весь фреймворк построен вокруг такого дизайна.
|
||||
|
||||
И это все тщательно протестировано еще на этапе разработки дизайна, до реализации, чтобы это работало со всеми редакторами.
|
||||
И это было тщательно протестировано ещё на этапе проектирования, до реализации, чтобы убедиться, что всё будет работать со всеми редакторами.
|
||||
|
||||
Для поддержки этого даже были внесены некоторые изменения в сам Pydantic.
|
||||
В сам Pydantic даже были внесены некоторые изменения для поддержки этого.
|
||||
|
||||
На всех предыдущих скриншотах используется <a href="https://code.visualstudio.com" class="external-link" target="_blank">Visual Studio Code</a>.
|
||||
Предыдущие скриншоты сделаны в <a href="https://code.visualstudio.com" class="external-link" target="_blank">Visual Studio Code</a>.
|
||||
|
||||
Но у вас будет такая же поддержка и с <a href="https://www.jetbrains.com/pycharm/" class="external-link" target="_blank">PyCharm</a>, и вообще с любым редактором Python:
|
||||
Но вы получите такую же поддержку редактора кода в <a href="https://www.jetbrains.com/pycharm/" class="external-link" target="_blank">PyCharm</a> и большинстве других редакторов Python:
|
||||
|
||||
<img src="/img/tutorial/body/image05.png">
|
||||
|
||||
/// tip | Подсказка
|
||||
/// tip | Совет
|
||||
|
||||
Если вы используете <a href="https://www.jetbrains.com/pycharm/" class="external-link" target="_blank">PyCharm</a> в качестве редактора, то вам стоит попробовать плагин <a href="https://github.com/koxudaxi/pydantic-pycharm-plugin/" class="external-link" target="_blank">Pydantic PyCharm Plugin</a>.
|
||||
Если вы используете <a href="https://www.jetbrains.com/pycharm/" class="external-link" target="_blank">PyCharm</a> в качестве редактора кода, вы можете использовать плагин <a href="https://github.com/koxudaxi/pydantic-pycharm-plugin/" class="external-link" target="_blank">Pydantic PyCharm Plugin</a>.
|
||||
|
||||
Он улучшает поддержку редактором моделей Pydantic в части:
|
||||
Он улучшает поддержку моделей Pydantic в редакторе кода, включая:
|
||||
|
||||
* автодополнения,
|
||||
* проверки типов,
|
||||
* рефакторинга,
|
||||
* поиска,
|
||||
* инспектирования.
|
||||
* автозавершение
|
||||
* проверки типов
|
||||
* рефакторинг
|
||||
* поиск
|
||||
* инспекции
|
||||
|
||||
///
|
||||
|
||||
## Использование модели
|
||||
## Использование модели { #use-the-model }
|
||||
|
||||
Внутри функции вам доступны все атрибуты объекта модели напрямую:
|
||||
|
||||
{* ../../docs_src/body/tutorial002.py hl[21] *}
|
||||
{* ../../docs_src/body/tutorial002_py310.py *}
|
||||
|
||||
## Тело запроса + параметры пути
|
||||
/// info | Информация
|
||||
|
||||
Вы можете одновременно объявлять параметры пути и тело запроса.
|
||||
В Pydantic v1 метод назывался `.dict()`, в Pydantic v2 он был помечен как устаревший (но всё ещё поддерживается) и переименован в `.model_dump()`.
|
||||
|
||||
**FastAPI** распознает, какие параметры функции соответствуют параметрам пути и должны быть **получены из пути**, а какие параметры функции, объявленные как модели Pydantic, должны быть **получены из тела запроса**.
|
||||
|
||||
{* ../../docs_src/body/tutorial003.py hl[17:18] *}
|
||||
|
||||
## Тело запроса + параметры пути + параметры запроса
|
||||
|
||||
Вы также можете одновременно объявить параметры для **пути**, **запроса** и **тела запроса**.
|
||||
|
||||
**FastAPI** распознает каждый из них и возьмет данные из правильного источника.
|
||||
|
||||
{* ../../docs_src/body/tutorial004.py hl[18] *}
|
||||
|
||||
Параметры функции распознаются следующим образом:
|
||||
|
||||
* Если параметр также указан в **пути**, то он будет использоваться как параметр пути.
|
||||
* Если аннотация типа параметра содержит **примитивный тип** (`int`, `float`, `str`, `bool` и т.п.), он будет интерпретирован как параметр **запроса**.
|
||||
* Если аннотация типа параметра представляет собой **модель Pydantic**, он будет интерпретирован как параметр **тела запроса**.
|
||||
|
||||
/// note | Заметка
|
||||
|
||||
FastAPI понимает, что значение параметра `q` не является обязательным, потому что имеет значение по умолчанию `= None`.
|
||||
|
||||
Аннотация `Optional` в `Optional[str]` не используется FastAPI, но помогает вашему редактору лучше понимать ваш код и обнаруживать ошибки.
|
||||
Примеры здесь используют `.dict()` для совместимости с Pydantic v1, но если вы можете использовать Pydantic v2, используйте `.model_dump()`.
|
||||
|
||||
///
|
||||
|
||||
## Без Pydantic
|
||||
## Тело запроса + параметры пути { #request-body-path-parameters }
|
||||
|
||||
Если вы не хотите использовать модели Pydantic, вы все еще можете использовать параметры **тела запроса**. Читайте в документации раздел [Тело - Несколько параметров: Единичные значения в теле](body-multiple-params.md#_2){.internal-link target=_blank}.
|
||||
Вы можете одновременно объявить параметры пути и тело запроса.
|
||||
|
||||
**FastAPI** распознает, что параметры функции, соответствующие параметрам пути, должны быть **получены из пути**, а параметры функции, объявленные как модели Pydantic, должны быть **получены из тела запроса**.
|
||||
|
||||
{* ../../docs_src/body/tutorial003_py310.py hl[15:16] *}
|
||||
|
||||
## Тело запроса + параметры пути + параметры запроса { #request-body-path-query-parameters }
|
||||
|
||||
Вы также можете одновременно объявить параметры **тела**, **пути** и **запроса**.
|
||||
|
||||
**FastAPI** распознает каждый из них и возьмёт данные из правильного источника.
|
||||
|
||||
{* ../../docs_src/body/tutorial004_py310.py hl[16] *}
|
||||
|
||||
Параметры функции будут распознаны следующим образом:
|
||||
|
||||
* Если параметр также объявлен в **пути**, он будет использоваться как параметр пути.
|
||||
* Если параметр имеет **скалярный тип** (например, `int`, `float`, `str`, `bool` и т. п.), он будет интерпретирован как параметр **запроса**.
|
||||
* Если параметр объявлен как тип **модели Pydantic**, он будет интерпретирован как **тело** запроса.
|
||||
|
||||
/// note | Заметка
|
||||
|
||||
FastAPI понимает, что значение `q` не является обязательным из-за значения по умолчанию `= None`.
|
||||
|
||||
Аннотации типов `str | None` (Python 3.10+) или `Union[str, None]` (Python 3.8+) не используются FastAPI для определения обязательности; он узнает, что параметр не обязателен, потому что у него есть значение по умолчанию `= None`.
|
||||
|
||||
Но добавление аннотаций типов позволит вашему редактору кода лучше вас поддерживать и обнаруживать ошибки.
|
||||
|
||||
///
|
||||
|
||||
## Без Pydantic { #without-pydantic }
|
||||
|
||||
Если вы не хотите использовать модели Pydantic, вы также можете использовать параметры **Body**. См. раздел документации [Тело — Несколько параметров: Единичные значения в теле](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Модели параметров cookie
|
||||
# Модели параметров cookie { #cookie-parameter-models }
|
||||
|
||||
Если у вас есть группа **cookies**, которые связаны между собой, вы можете создать **Pydantic-модель** для их объявления. 🍪
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
///
|
||||
|
||||
## Pydantic-модель для cookies
|
||||
## Pydantic-модель для cookies { #cookies-with-a-pydantic-model }
|
||||
|
||||
Объявите параметры **cookie**, которые вам нужны, в **Pydantic-модели**, а затем объявите параметр как `Cookie`:
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
**FastAPI** **извлечёт** данные для **каждого поля** из **cookies**, полученных в запросе, и выдаст вам объявленную Pydantic-модель.
|
||||
|
||||
## Проверка сгенерированной документации
|
||||
## Проверка сгенерированной документации { #check-the-docs }
|
||||
|
||||
Вы можете посмотреть объявленные cookies в графическом интерфейсе Документации по пути `/docs`:
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
|
||||
///
|
||||
|
||||
## Запрет дополнительных cookies
|
||||
## Запрет дополнительных cookies { #forbid-extra-cookies }
|
||||
|
||||
В некоторых случаях (не особо часто встречающихся) вам может понадобиться **ограничить** cookies, которые вы хотите получать.
|
||||
|
||||
@@ -65,12 +65,12 @@
|
||||
"type": "extra_forbidden",
|
||||
"loc": ["cookie", "santa_tracker"],
|
||||
"msg": "Extra inputs are not permitted",
|
||||
"input": "good-list-please"
|
||||
"input": "good-list-please",
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Заключение
|
||||
## Заключение { #summary }
|
||||
|
||||
Вы можете использовать **Pydantic-модели** для объявления <abbr title="Съешьте последнюю печеньку, прежде чем уйти. 🍪">**cookies**</abbr> в **FastAPI**. 😎
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
# Параметры Cookie
|
||||
# Параметры Cookie { #cookie-parameters }
|
||||
|
||||
Вы можете задать параметры Cookie таким же способом, как `Query` и `Path` параметры.
|
||||
|
||||
## Импорт `Cookie`
|
||||
## Импорт `Cookie` { #import-cookie }
|
||||
|
||||
Сначала импортируйте `Cookie`:
|
||||
|
||||
{* ../../docs_src/cookie_params/tutorial001_py310.py hl[1] *}
|
||||
{* ../../docs_src/cookie_params/tutorial001_an_py310.py hl[3] *}
|
||||
|
||||
## Объявление параметров `Cookie`
|
||||
## Объявление параметров `Cookie` { #declare-cookie-parameters }
|
||||
|
||||
Затем объявляйте параметры cookie, используя ту же структуру, что и с `Path` и `Query`.
|
||||
|
||||
Первое значение - это значение по умолчанию, вы можете передать все дополнительные параметры проверки или аннотации:
|
||||
Вы можете задать значение по умолчанию, а также все дополнительные параметры валидации или аннотации:
|
||||
|
||||
{* ../../docs_src/cookie_params/tutorial001_py310.py hl[7] *}
|
||||
{* ../../docs_src/cookie_params/tutorial001_an_py310.py hl[9] *}
|
||||
|
||||
/// note | Технические детали
|
||||
|
||||
@@ -30,6 +30,16 @@
|
||||
|
||||
///
|
||||
|
||||
## Резюме
|
||||
/// info | Дополнительная информация
|
||||
|
||||
Имейте в виду, что, поскольку браузеры обрабатывают cookies особым образом и «за кулисами», они не позволяют JavaScript просто так получать к ним доступ.
|
||||
|
||||
Если вы откроете интерфейс документации API на `/docs`, вы сможете увидеть документацию по cookies для ваших операций пути.
|
||||
|
||||
Но даже если вы заполните данные и нажмёте «Execute», поскольку UI документации работает с JavaScript, cookies отправлены не будут, и вы увидите сообщение об ошибке, как будто вы не указали никаких значений.
|
||||
|
||||
///
|
||||
|
||||
## Резюме { #recap }
|
||||
|
||||
Объявляйте cookies с помощью `Cookie`, используя тот же общий шаблон, что и `Query`, и `Path`.
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# CORS (Cross-Origin Resource Sharing)
|
||||
# CORS (Cross-Origin Resource Sharing) { #cors-cross-origin-resource-sharing }
|
||||
|
||||
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" class="external-link" target="_blank">Понятие CORS или "Cross-Origin Resource Sharing"</a> относится к ситуациям, при которых запущенный в браузере фронтенд содержит JavaScript-код, который взаимодействует с бэкендом, находящимся на другом "источнике" ("origin").
|
||||
|
||||
## Источник
|
||||
## Источник { #origin }
|
||||
|
||||
Источник - это совокупность протокола (`http`, `https`), домена (`myapp.com`, `localhost`, `localhost.tiangolo.com`) и порта (`80`, `443`, `8080`).
|
||||
Источник — это совокупность протокола (`http`, `https`), домена (`myapp.com`, `localhost`, `localhost.tiangolo.com`) и порта (`80`, `443`, `8080`).
|
||||
|
||||
Поэтому это три разных источника:
|
||||
|
||||
@@ -12,69 +12,72 @@
|
||||
* `https://localhost`
|
||||
* `http://localhost:8080`
|
||||
|
||||
Даже если они все расположены в `localhost`, они используют разные протоколы и порты, а значит, являются разными источниками.
|
||||
Даже если они все расположены в `localhost`, они используют разные протоколы или порты, а значит, являются разными источниками.
|
||||
|
||||
## Шаги
|
||||
## Шаги { #steps }
|
||||
|
||||
Допустим, у вас есть фронтенд, запущенный в браузере по адресу `http://localhost:8080`, и его JavaScript-код пытается взаимодействовать с бэкендом, запущенным по адресу `http://localhost` (поскольку мы не указали порт, браузер по умолчанию будет использовать порт `80`).
|
||||
Допустим, у вас есть фронтенд, запущенный в браузере по адресу `http://localhost:8080`, и его JavaScript-код пытается взаимодействовать с бэкендом, запущенным по адресу `http://localhost` (поскольку мы не указали порт, браузер по умолчанию будет использовать порт `80`).
|
||||
|
||||
Затем браузер отправит бэкенду HTTP-запрос `OPTIONS`, и если бэкенд вернёт соответствующие заголовки для авторизации взаимодействия с другим источником (`http://localhost:8080`), то браузер разрешит JavaScript-коду на фронтенде отправить запрос на этот бэкенд.
|
||||
Затем браузер отправит на бэкенд на `:80` HTTP-запрос `OPTIONS`, и если бэкенд вернёт соответствующие HTTP-заголовки, авторизующие взаимодействие с другим источником (`http://localhost:8080`), то браузер на `:8080` разрешит JavaScript на фронтенде отправить свой запрос на бэкенд на `:80`.
|
||||
|
||||
Чтобы это работало, у бэкенда должен быть список "разрешённых источников" ("allowed origins").
|
||||
Чтобы это работало, у бэкенда на `:80` должен быть список "разрешённых источников" ("allowed origins").
|
||||
|
||||
В таком случае этот список должен содержать `http://localhost:8080`, чтобы фронтенд работал корректно.
|
||||
В таком случае этот список должен содержать `http://localhost:8080`, чтобы фронтенд на `:8080` работал корректно.
|
||||
|
||||
## Подстановочный символ `"*"`
|
||||
## Подстановочный символ "*" { #wildcards }
|
||||
|
||||
В качестве списка источников можно указать подстановочный символ `"*"` ("wildcard"), чтобы разрешить любые источники.
|
||||
|
||||
Но тогда не будут разрешены некоторые виды взаимодействия, включая всё связанное с учётными данными: куки, заголовки Authorization с Bearer-токенами наподобие тех, которые мы использовали ранее и т.п.
|
||||
Но тогда будут разрешены только некоторые виды взаимодействия, и всё, что связано с учётными данными, будет исключено: куки, HTTP-заголовки Authorization, как при использовании Bearer-токенов, и т.п.
|
||||
|
||||
Поэтому, чтобы всё работало корректно, лучше явно указывать список разрешённых источников.
|
||||
|
||||
## Использование `CORSMiddleware`
|
||||
## Использование `CORSMiddleware` { #use-corsmiddleware }
|
||||
|
||||
Вы можете настроить этот механизм в вашем **FastAPI** приложении, используя `CORSMiddleware`.
|
||||
Вы можете настроить это в вашем **FastAPI**-приложении, используя `CORSMiddleware`.
|
||||
|
||||
* Импортируйте `CORSMiddleware`.
|
||||
* Создайте список разрешённых источников (в виде строк).
|
||||
* Добавьте его как "middleware" к вашему **FastAPI** приложению.
|
||||
* Добавьте его как "middleware" (промежуточный слой) к вашему **FastAPI**-приложению.
|
||||
|
||||
Вы также можете указать, разрешает ли ваш бэкенд использование:
|
||||
|
||||
* Учётных данных (включая заголовки Authorization, куки и т.п.).
|
||||
* Учётных данных (HTTP-заголовки Authorization, куки и т.п.).
|
||||
* Отдельных HTTP-методов (`POST`, `PUT`) или всех вместе, используя `"*"`.
|
||||
* Отдельных HTTP-заголовков или всех вместе, используя `"*"`.
|
||||
|
||||
{* ../../docs_src/cors/tutorial001.py hl[2,6:11,13:19] *}
|
||||
|
||||
`CORSMiddleware` использует для параметров "запрещающие" значения по умолчанию, поэтому вам нужно явным образом разрешить использование отдельных источников, методов или заголовков, чтобы браузеры могли использовать их в кросс-доменном контексте.
|
||||
`CORSMiddleware` использует "запрещающие" значения по умолчанию, поэтому вам нужно явным образом разрешить использование отдельных источников, методов или заголовков, чтобы браузеры могли использовать их в кросс-доменном контексте.
|
||||
|
||||
Поддерживаются следующие аргументы:
|
||||
|
||||
* `allow_origins` - Список источников, на которые разрешено выполнять кросс-доменные запросы. Например, `['https://example.org', 'https://www.example.org']`. Можно использовать `['*']`, чтобы разрешить любые источники.
|
||||
* `allow_origin_regex` - Регулярное выражение для определения источников, на которые разрешено выполнять кросс-доменные запросы. Например, `'https://.*\.example\.org'`.
|
||||
* `allow_methods` - Список HTTP-методов, которые разрешены для кросс-доменных запросов. По умолчанию равно `['GET']`. Можно использовать `['*']`, чтобы разрешить все стандартные методы.
|
||||
* `allow_headers` - Список HTTP-заголовков, которые должны поддерживаться при кросс-доменных запросах. По умолчанию равно `[]`. Можно использовать `['*']`, чтобы разрешить все заголовки. Заголовки `Accept`, `Accept-Language`, `Content-Language` и `Content-Type` всегда разрешены для <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests" class="external-link" rel="noopener" target="_blank">простых CORS-запросов</a>.
|
||||
* `allow_credentials` - указывает, что куки разрешены в кросс-доменных запросах. По умолчанию равно `False`. Также, `allow_origins` нельзя присвоить `['*']`, если разрешено использование учётных данных. В таком случае должен быть указан список источников.
|
||||
* `expose_headers` - Указывает любые заголовки ответа, которые должны быть доступны браузеру. По умолчанию равно `[]`.
|
||||
* `max_age` - Устанавливает максимальное время в секундах, в течение которого браузер кэширует CORS-ответы. По умолчанию равно `600`.
|
||||
* `allow_origin_regex` - Регулярное выражение для определения источников, на которые разрешено выполнять кросс-доменные запросы. Например, `'https://.*\.example\.org'`.
|
||||
* `allow_methods` - Список HTTP-методов, которые разрешены для кросс-доменных запросов. По умолчанию `['GET']`. Можно использовать `['*']`, чтобы разрешить все стандартные методы.
|
||||
* `allow_headers` - Список HTTP-заголовков запроса, которые должны поддерживаться при кросс-доменных запросах. По умолчанию `[]`. Можно использовать `['*']`, чтобы разрешить все заголовки. Заголовки `Accept`, `Accept-Language`, `Content-Language` и `Content-Type` всегда разрешены для <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests" class="external-link" rel="noopener" target="_blank">простых CORS-запросов</a>.
|
||||
* `allow_credentials` - Указывает, что куки разрешены в кросс-доменных запросах. По умолчанию `False`.
|
||||
|
||||
Ни один из параметров `allow_origins`, `allow_methods` и `allow_headers` не может быть установлен в `['*']`, если `allow_credentials` имеет значение `True`. Все они должны быть <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#credentialed_requests_and_wildcards" class="external-link" rel="noopener" target="_blank">указаны явно</a>.
|
||||
|
||||
* `expose_headers` - Указывает любые заголовки ответа, которые должны быть доступны браузеру. По умолчанию `[]`.
|
||||
* `max_age` - Устанавливает максимальное время в секундах, в течение которого браузер кэширует CORS-ответы. По умолчанию `600`.
|
||||
|
||||
`CORSMiddleware` отвечает на два типа HTTP-запросов...
|
||||
|
||||
### CORS-запросы с предварительной проверкой
|
||||
### CORS-запросы с предварительной проверкой { #cors-preflight-requests }
|
||||
|
||||
Это любые `OPTIONS` запросы с заголовками `Origin` и `Access-Control-Request-Method`.
|
||||
Это любые `OPTIONS`-запросы с заголовками `Origin` и `Access-Control-Request-Method`.
|
||||
|
||||
В этом случае middleware перехватит входящий запрос и отправит соответствующие CORS-заголовки в ответе, а также ответ `200` или `400` в информационных целях.
|
||||
|
||||
### Простые запросы
|
||||
### Простые запросы { #simple-requests }
|
||||
|
||||
Любые запросы с заголовком `Origin`. В этом случае middleware передаст запрос дальше как обычно, но добавит соответствующие CORS-заголовки к ответу.
|
||||
|
||||
## Больше информации
|
||||
## Больше информации { #more-info }
|
||||
|
||||
Для получения более подробной информации о <abbr title="Cross-Origin Resource Sharing">CORS</abbr>, обратитесь к <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" class="external-link" target="_blank">Документации CORS от Mozilla</a>.
|
||||
Для получения более подробной информации о <abbr title="Cross-Origin Resource Sharing – совместное использование ресурсов между источниками">CORS</abbr> обратитесь к <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" class="external-link" target="_blank">документации CORS от Mozilla</a>.
|
||||
|
||||
/// note | Технические детали
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
# Отладка
|
||||
# Отладка { #debugging }
|
||||
|
||||
Вы можете подключить отладчик в своем редакторе, например, в Visual Studio Code или PyCharm.
|
||||
|
||||
## Вызов `uvicorn`
|
||||
## Вызов `uvicorn` { #call-uvicorn }
|
||||
|
||||
В вашем FastAPI приложении, импортируйте и вызовите `uvicorn` напрямую:
|
||||
|
||||
{* ../../docs_src/debugging/tutorial001.py hl[1,15] *}
|
||||
|
||||
### Описание `__name__ == "__main__"`
|
||||
### Описание `__name__ == "__main__"` { #about-name-main }
|
||||
|
||||
Главная цель использования `__name__ == "__main__"` в том, чтобы код выполнялся при запуске файла с помощью:
|
||||
|
||||
@@ -26,7 +26,7 @@ $ python myapp.py
|
||||
from myapp import app
|
||||
```
|
||||
|
||||
#### Больше деталей
|
||||
#### Больше деталей { #more-details }
|
||||
|
||||
Давайте назовём ваш файл `myapp.py`.
|
||||
|
||||
@@ -78,7 +78,7 @@ from myapp import app
|
||||
|
||||
///
|
||||
|
||||
## Запуск вашего кода с помощью отладчика
|
||||
## Запуск вашего кода с помощью отладчика { #run-your-code-with-your-debugger }
|
||||
|
||||
Так как вы запускаете сервер Uvicorn непосредственно из вашего кода, вы можете вызвать Python программу (ваше FastAPI приложение) напрямую из отладчика.
|
||||
|
||||
|
||||
@@ -1,28 +1,30 @@
|
||||
# Классы как зависимости
|
||||
# Классы как зависимости { #classes-as-dependencies }
|
||||
|
||||
Прежде чем углубиться в систему **Внедрения Зависимостей**, давайте обновим предыдущий пример.
|
||||
|
||||
## `Словарь` из предыдущего примера
|
||||
## `dict` из предыдущего примера { #a-dict-from-the-previous-example }
|
||||
|
||||
В предыдущем примере мы возвращали `словарь` из нашей зависимости:
|
||||
В предыдущем примере мы возвращали `dict` из нашей зависимости:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[9] *}
|
||||
|
||||
Но затем мы получаем `словарь` в параметре `commons` *функции операции пути*. И мы знаем, что редакторы не могут обеспечить достаточную поддержку для `словаря`, поскольку они не могут знать их ключи и типы значений.
|
||||
Но затем мы получаем `dict` в параметре `commons` *функции-обработчика пути*.
|
||||
|
||||
И мы знаем, что редакторы кода не могут обеспечить достаточную поддержку (например, автозавершение) для `dict`, поскольку они не могут знать их ключи и типы значений.
|
||||
|
||||
Мы можем сделать лучше...
|
||||
|
||||
## Что делает зависимость
|
||||
## Что делает зависимость { #what-makes-a-dependency }
|
||||
|
||||
До сих пор вы видели зависимости, объявленные как функции.
|
||||
|
||||
Но это не единственный способ объявления зависимостей (хотя, вероятно, более распространенный).
|
||||
Но это не единственный способ объявления зависимостей (хотя он, вероятно, более распространенный).
|
||||
|
||||
Ключевым фактором является то, что зависимость должна быть "вызываемой".
|
||||
Ключевым фактором является то, что зависимость должна быть «вызываемой».
|
||||
|
||||
В Python "**вызываемый**" - это все, что Python может "вызвать", как функцию.
|
||||
В Python «**вызываемый**» — это всё, что Python может «вызвать», как функцию.
|
||||
|
||||
Так, если у вас есть объект `something` (который может _не_ быть функцией) и вы можете "вызвать" его (выполнить) как:
|
||||
Так, если у вас есть объект `something` (который может и _не_ быть функцией) и вы можете «вызвать» его (выполнить) так:
|
||||
|
||||
```Python
|
||||
something()
|
||||
@@ -34,9 +36,9 @@ something()
|
||||
something(some_argument, some_keyword_argument="foo")
|
||||
```
|
||||
|
||||
в таком случае он является "вызываемым".
|
||||
в таком случае он является «вызываемым».
|
||||
|
||||
## Классы как зависимости
|
||||
## Классы как зависимости { #classes-as-dependencies_1 }
|
||||
|
||||
Вы можете заметить, что для создания экземпляра класса в Python используется тот же синтаксис.
|
||||
|
||||
@@ -53,17 +55,17 @@ fluffy = Cat(name="Mr Fluffy")
|
||||
|
||||
В данном случае `fluffy` является экземпляром класса `Cat`.
|
||||
|
||||
А чтобы создать `fluffy`, вы "вызываете" `Cat`.
|
||||
А чтобы создать `fluffy`, вы «вызываете» `Cat`.
|
||||
|
||||
Таким образом, класс в Python также является **вызываемым**.
|
||||
|
||||
Тогда в **FastAPI** в качестве зависимости можно использовать класс Python.
|
||||
|
||||
На самом деле FastAPI проверяет, что переданный объект является "вызываемым" (функция, класс или что-либо еще) и указаны необходимые для его вызова параметры.
|
||||
На самом деле FastAPI проверяет, что переданный объект является «вызываемым» (функция, класс или что-либо еще) и какие параметры у него определены.
|
||||
|
||||
Если вы передаёте что-то, что можно "вызывать" в качестве зависимости в **FastAPI**, то он будет анализировать параметры, необходимые для "вызова" этого объекта и обрабатывать их так же, как параметры *функции операции пути*. Включая подзависимости.
|
||||
Если вы передаёте «вызываемый» объект в качестве зависимости в **FastAPI**, он проанализирует параметры, необходимые для этого «вызываемого» объекта, и обработает их так же, как параметры *функции-обработчика пути*. Включая подзависимости.
|
||||
|
||||
Это относится и к вызываемым объектам без параметров. Работа с ними происходит точно так же, как и для *функций операции пути* без параметров.
|
||||
Это относится и к вызываемым объектам без параметров. Работа с ними происходит точно так же, как и для *функций-обработчиков пути* без параметров.
|
||||
|
||||
Теперь мы можем изменить зависимость `common_parameters`, указанную выше, на класс `CommonQueryParams`:
|
||||
|
||||
@@ -73,37 +75,45 @@ fluffy = Cat(name="Mr Fluffy")
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[12] *}
|
||||
|
||||
...имеет те же параметры, что и ранее используемая функция `common_parameters`:
|
||||
...он имеет те же параметры, что и ранее используемая функция `common_parameters`:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[8] *}
|
||||
|
||||
Эти параметры и будут использоваться **FastAPI** для "решения" зависимости.
|
||||
Эти параметры и будут использоваться **FastAPI** для «решения» зависимости.
|
||||
|
||||
В обоих случаях она будет иметь:
|
||||
|
||||
* Необязательный параметр запроса `q`, представляющий собой `str`.
|
||||
* Параметр запроса `skip`, представляющий собой `int`, по умолчанию `0`.
|
||||
* Параметр запроса `limit`, представляющий собой `int`, по умолчанию равный `100`.
|
||||
* Параметр запроса `limit`, представляющий собой `int`, по умолчанию `100`.
|
||||
|
||||
В обоих случаях данные будут конвертированы, валидированы, документированы по схеме OpenAPI и т.д.
|
||||
В обоих случаях данные будут конвертированы, валидированы, задокументированы в схеме OpenAPI и т.д.
|
||||
|
||||
## Как это использовать
|
||||
## Как это использовать { #use-it }
|
||||
|
||||
Теперь вы можете объявить свою зависимость, используя этот класс.
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[19] *}
|
||||
|
||||
**FastAPI** вызывает класс `CommonQueryParams`. При этом создается "экземпляр" этого класса, который будет передан в качестве параметра `commons` в вашу функцию.
|
||||
**FastAPI** вызывает класс `CommonQueryParams`. При этом создается «экземпляр» этого класса, который будет передан в качестве параметра `commons` в вашу функцию.
|
||||
|
||||
## Аннотация типа или `Depends`
|
||||
## Аннотация типа и `Depends` { #type-annotation-vs-depends }
|
||||
|
||||
Обратите внимание, что в приведенном выше коде мы два раза пишем `CommonQueryParams`:
|
||||
|
||||
//// tab | Python 3.6+ без Annotated
|
||||
//// tab | Python 3.8+
|
||||
|
||||
```Python
|
||||
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+ non-Annotated
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Рекомендуется использовать версию с `Annotated` если возможно.
|
||||
Рекомендуется использовать версию с `Annotated`, если возможно.
|
||||
|
||||
///
|
||||
|
||||
@@ -113,15 +123,7 @@ commons: CommonQueryParams = Depends(CommonQueryParams)
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.6+
|
||||
|
||||
```Python
|
||||
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
Последний параметр `CommonQueryParams`, в:
|
||||
Последний `CommonQueryParams`, в:
|
||||
|
||||
```Python
|
||||
... Depends(CommonQueryParams)
|
||||
@@ -129,13 +131,13 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
|
||||
|
||||
...это то, что **FastAPI** будет использовать, чтобы узнать, что является зависимостью.
|
||||
|
||||
Из него FastAPI извлечёт объявленные параметры и именно их будет вызывать.
|
||||
Из него FastAPI извлечёт объявленные параметры, и именно его FastAPI будет вызывать.
|
||||
|
||||
---
|
||||
|
||||
В этом случае первый `CommonQueryParams`, в:
|
||||
|
||||
//// tab | Python 3.6+
|
||||
//// tab | Python 3.8+
|
||||
|
||||
```Python
|
||||
commons: Annotated[CommonQueryParams, ...
|
||||
@@ -143,11 +145,11 @@ commons: Annotated[CommonQueryParams, ...
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.6+ без Annotated
|
||||
//// tab | Python 3.8+ non-Annotated
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Рекомендуется использовать версию с `Annotated` если возможно.
|
||||
Рекомендуется использовать версию с `Annotated`, если возможно.
|
||||
|
||||
///
|
||||
|
||||
@@ -161,7 +163,7 @@ commons: CommonQueryParams ...
|
||||
|
||||
На самом деле можно написать просто:
|
||||
|
||||
//// tab | Python 3.6+
|
||||
//// tab | Python 3.8+
|
||||
|
||||
```Python
|
||||
commons: Annotated[Any, Depends(CommonQueryParams)]
|
||||
@@ -169,11 +171,11 @@ commons: Annotated[Any, Depends(CommonQueryParams)]
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.6+ без Annotated
|
||||
//// tab | Python 3.8+ non-Annotated
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Рекомендуется использовать версию с `Annotated` если возможно.
|
||||
Рекомендуется использовать версию с `Annotated`, если возможно.
|
||||
|
||||
///
|
||||
|
||||
@@ -187,19 +189,27 @@ commons = Depends(CommonQueryParams)
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial003_an_py310.py hl[19] *}
|
||||
|
||||
Но объявление типа приветствуется, так как в этом случае ваш редактор будет знать, что будет передано в качестве параметра `commons`, и тогда он сможет помочь вам с автодополнением, проверкой типов и т.д:
|
||||
Но объявление типа приветствуется, так как в этом случае ваш редактор кода будет знать, что будет передано в качестве параметра `commons`, и тогда он сможет помочь вам с автозавершением, проверкой типов и т.д.:
|
||||
|
||||
<img src="/img/tutorial/dependencies/image02.png">
|
||||
|
||||
## Сокращение
|
||||
## Сокращение { #shortcut }
|
||||
|
||||
Но вы видите, что здесь мы имеем некоторое повторение кода, дважды написав `CommonQueryParams`:
|
||||
|
||||
//// tab | Python 3.6+ без Annotated
|
||||
//// tab | Python 3.8+
|
||||
|
||||
```Python
|
||||
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+ non-Annotated
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Рекомендуется использовать версию с `Annotated` если возможно.
|
||||
Рекомендуется использовать версию с `Annotated`, если возможно.
|
||||
|
||||
///
|
||||
|
||||
@@ -209,20 +219,13 @@ commons: CommonQueryParams = Depends(CommonQueryParams)
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.6+
|
||||
|
||||
```Python
|
||||
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
Для случаев, когда зависимостью является *конкретный* класс, который **FastAPI** "вызовет" для создания экземпляра этого класса, можно использовать укороченную запись.
|
||||
**FastAPI** предоставляет сокращение для таких случаев, когда зависимость — это *конкретный* класс, который **FastAPI** будет «вызывать» для создания экземпляра этого класса.
|
||||
|
||||
Для этих конкретных случаев вы можете сделать следующее.
|
||||
|
||||
Вместо того чтобы писать:
|
||||
|
||||
//// tab | Python 3.6+
|
||||
//// tab | Python 3.8+
|
||||
|
||||
```Python
|
||||
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
|
||||
@@ -230,11 +233,11 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.6+ без Annotated
|
||||
//// tab | Python 3.8+ non-Annotated
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Рекомендуется использовать версию с `Annotated` если возможно.
|
||||
Рекомендуется использовать версию с `Annotated`, если возможно.
|
||||
|
||||
///
|
||||
|
||||
@@ -246,7 +249,7 @@ commons: CommonQueryParams = Depends(CommonQueryParams)
|
||||
|
||||
...следует написать:
|
||||
|
||||
//// tab | Python 3.6+
|
||||
//// tab | Python 3.8+
|
||||
|
||||
```Python
|
||||
commons: Annotated[CommonQueryParams, Depends()]
|
||||
@@ -254,11 +257,11 @@ commons: Annotated[CommonQueryParams, Depends()]
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.6 без Annotated
|
||||
//// tab | Python 3.8 non-Annotated
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Рекомендуется использовать версию с `Annotated` если возможно.
|
||||
Рекомендуется использовать версию с `Annotated`, если возможно.
|
||||
|
||||
///
|
||||
|
||||
@@ -278,7 +281,7 @@ commons: CommonQueryParams = Depends()
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Если это покажется вам более запутанным, чем полезным, не обращайте внимания, это вам не *нужно*.
|
||||
Если это покажется вам более запутанным, чем полезным, не обращайте внимания — это вам не *нужно*.
|
||||
|
||||
Это просто сокращение. Потому что **FastAPI** заботится о том, чтобы помочь вам свести к минимуму повторение кода.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Зависимости в декораторах операции пути
|
||||
# Зависимости в декораторах операции пути { #dependencies-in-path-operation-decorators }
|
||||
|
||||
В некоторых случаях, возвращаемое значение зависимости не используется внутри *функции операции пути*.
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
Для таких ситуаций, вместо объявления *функции операции пути* с параметром `Depends`, вы можете добавить список зависимостей `dependencies` в *декоратор операции пути*.
|
||||
|
||||
## Добавление `dependencies` в *декоратор операции пути*
|
||||
## Добавление `dependencies` (зависимостей) в *декоратор операции пути* { #add-dependencies-to-the-path-operation-decorator }
|
||||
|
||||
*Декоратор операции пути* получает необязательный аргумент `dependencies`.
|
||||
|
||||
@@ -36,23 +36,23 @@
|
||||
|
||||
///
|
||||
|
||||
## Исключения в dependencies и возвращаемые значения
|
||||
## Исключения в Зависимостях и возвращаемые значения { #dependencies-errors-and-return-values }
|
||||
|
||||
Вы можете использовать те же *функции* зависимостей, что и обычно.
|
||||
|
||||
### Требования к зависимостям
|
||||
### Требования к зависимостям { #dependency-requirements }
|
||||
|
||||
Они могут объявлять требования к запросу (например заголовки) или другие подзависимости:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[8,13] *}
|
||||
|
||||
### Вызов исключений
|
||||
### Вызов исключений { #raise-exceptions }
|
||||
|
||||
Зависимости из dependencies могут вызывать исключения с помощью `raise`, как и обычные зависимости:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[10,15] *}
|
||||
|
||||
### Возвращаемые значения
|
||||
### Возвращаемые значения { #return-values }
|
||||
|
||||
И они могут возвращать значения или нет, эти значения использоваться не будут.
|
||||
|
||||
@@ -60,10 +60,10 @@
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[11,16] *}
|
||||
|
||||
## Dependencies для группы *операций путей*
|
||||
## Зависимости для группы *операций путей* { #dependencies-for-a-group-of-path-operations }
|
||||
|
||||
Позже, читая о том как структурировать большие приложения ([Bigger Applications - Multiple Files](../../tutorial/bigger-applications.md){.internal-link target=_blank}), возможно, многофайловые, вы узнаете как объявить единый параметр `dependencies` для всей группы *операций путей*.
|
||||
Позже, читая о том как структурировать большие приложения ([Большие приложения — несколько файлов](../../tutorial/bigger-applications.md){.internal-link target=_blank}), возможно, многофайловые, вы узнаете как объявить единый параметр `dependencies` для всей группы *операций путей*.
|
||||
|
||||
## Глобальный Dependencies
|
||||
## Глобальные Зависимости { #global-dependencies }
|
||||
|
||||
Далее мы увидим, как можно добавить dependencies для всего `FastAPI` приложения, так чтобы они применялись к каждой *операции пути*.
|
||||
|
||||
@@ -1,125 +1,139 @@
|
||||
# Зависимости с yield
|
||||
# Зависимости с yield { #dependencies-with-yield }
|
||||
|
||||
FastAPI поддерживает зависимости, которые выполняют некоторые <abbr title='также известные как "exit", "cleanup", "teardown", "close", "context managers", ...'>дополнительные действия после завершения работы</abbr>.
|
||||
FastAPI поддерживает зависимости, которые выполняют некоторые <abbr title='иногда также называемые "exit code", "cleanup code", "teardown code", "closing code", "context manager exit code" и т.п.'>дополнительные шаги после завершения</abbr>.
|
||||
|
||||
Для этого используйте `yield` вместо `return`, а дополнительный код напишите после него.
|
||||
Для этого используйте `yield` вместо `return`, а дополнительные шаги (код) напишите после него.
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Обязательно используйте `yield` один-единственный раз.
|
||||
Убедитесь, что используете `yield` только один раз на одну зависимость.
|
||||
|
||||
///
|
||||
|
||||
/// note | Технические детали
|
||||
|
||||
Любая функция, с которой может работать:
|
||||
Любая функция, с которой можно корректно использовать:
|
||||
|
||||
* <a href="https://docs.python.org/3/library/contextlib.html#contextlib.contextmanager" class="external-link" target="_blank">`@contextlib.contextmanager`</a> или
|
||||
* <a href="https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager" class="external-link" target="_blank">`@contextlib.asynccontextmanager`</a>
|
||||
|
||||
будет корректно использоваться в качестве **FastAPI**-зависимости.
|
||||
будет корректной для использования в качестве зависимости **FastAPI**.
|
||||
|
||||
На самом деле, FastAPI использует эту пару декораторов "под капотом".
|
||||
На самом деле, FastAPI использует эти два декоратора внутренне.
|
||||
|
||||
///
|
||||
|
||||
## Зависимость базы данных с помощью `yield`
|
||||
## Зависимость базы данных с помощью `yield` { #a-database-dependency-with-yield }
|
||||
|
||||
Например, с его помощью можно создать сессию работы с базой данных и закрыть его после завершения.
|
||||
Например, с его помощью можно создать сессию работы с базой данных и закрыть её после завершения.
|
||||
|
||||
Перед созданием ответа будет выполнен только код до и включая `yield`.
|
||||
Перед созданием ответа будет выполнен только код до и включая оператор `yield`:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial007.py hl[2:4] *}
|
||||
|
||||
Полученное значение и есть то, что будет внедрено в функцию операции пути и другие зависимости:
|
||||
Значение, полученное из `yield`, внедряется в *операции пути* и другие зависимости:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial007.py hl[4] *}
|
||||
|
||||
Код, следующий за оператором `yield`, выполняется после доставки ответа:
|
||||
Код, следующий за оператором `yield`, выполняется после ответа:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial007.py hl[5:6] *}
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Можно использовать как `async` так и обычные функции.
|
||||
Можно использовать как `async`, так и обычные функции.
|
||||
|
||||
**FastAPI** это корректно обработает, и в обоих случаях будет делать то же самое, что и с обычными зависимостями.
|
||||
**FastAPI** корректно обработает каждый вариант, так же как и с обычными зависимостями.
|
||||
|
||||
///
|
||||
|
||||
## Зависимость с `yield` и `try` одновременно
|
||||
## Зависимость с `yield` и `try` { #a-dependency-with-yield-and-try }
|
||||
|
||||
Если использовать блок `try` в зависимости с `yield`, то будет получено всякое исключение, которое было выброшено при использовании зависимости.
|
||||
Если использовать блок `try` в зависимости с `yield`, то вы получите любое исключение, которое было выброшено при использовании зависимости.
|
||||
|
||||
Например, если какой-то код в какой-то момент в середине, в другой зависимости или в *функции операции пути*, сделал "откат" транзакции базы данных или создал любую другую ошибку, то вы получите исключение в своей зависимости.
|
||||
Например, если какой-то код в какой-то момент в середине, в другой зависимости или в *операции пути*, сделал "откат" транзакции базы данных или создал любую другую ошибку, то вы получите это исключение в своей зависимости.
|
||||
|
||||
Таким образом, можно искать конкретное исключение внутри зависимости с помощью `except SomeException`.
|
||||
|
||||
Таким же образом можно использовать `finally`, чтобы убедиться, что обязательные шаги при выходе выполнены, независимо от того, было ли исключение или нет.
|
||||
Точно так же можно использовать `finally`, чтобы убедиться, что обязательные шаги при выходе выполнены независимо от того, было ли исключение или нет.
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial007.py hl[3,5] *}
|
||||
|
||||
## Подзависимости с `yield`
|
||||
## Подзависимости с `yield` { #sub-dependencies-with-yield }
|
||||
|
||||
Вы можете иметь подзависимости и "деревья" подзависимостей любого размера и формы, и любая из них или все они могут использовать `yield`.
|
||||
|
||||
**FastAPI** будет следить за тем, чтобы "код по выходу" в каждой зависимости с `yield` выполнялся в правильном порядке.
|
||||
**FastAPI** проследит за тем, чтобы «код выхода» в каждой зависимости с `yield` выполнялся в правильном порядке.
|
||||
|
||||
Например, `dependency_c` может иметь зависимость от `dependency_b`, а `dependency_b` от `dependency_a`:
|
||||
Например, `dependency_c` может зависеть от `dependency_b`, а `dependency_b` — от `dependency_a`:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[6,14,22] *}
|
||||
|
||||
И все они могут использовать `yield`.
|
||||
|
||||
В этом случае `dependency_c` для выполнения своего кода выхода нуждается в том, чтобы значение из `dependency_b` (здесь `dep_b`) было еще доступно.
|
||||
В этом случае `dependency_c` для выполнения своего кода выхода нуждается в том, чтобы значение из `dependency_b` (здесь `dep_b`) всё ещё было доступно.
|
||||
|
||||
И, в свою очередь, `dependency_b` нуждается в том, чтобы значение из `dependency_a` (здесь `dep_a`) было доступно для ее завершающего кода.
|
||||
И, в свою очередь, `dependency_b` нуждается в том, чтобы значение из `dependency_a` (здесь `dep_a`) было доступно для её кода выхода.
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[18:19,26:27] *}
|
||||
|
||||
Точно так же можно иметь часть зависимостей с `yield`, часть с `return`, и какие-то из них могут зависеть друг от друга.
|
||||
Точно так же можно иметь часть зависимостей с `yield`, часть — с `return`, и какие-то из них могут зависеть друг от друга.
|
||||
|
||||
Либо у вас может быть одна зависимость, которая требует несколько других зависимостей с `yield` и т.д.
|
||||
|
||||
Комбинации зависимостей могут быть какими вам угодно.
|
||||
Комбинации зависимостей могут быть какими угодно.
|
||||
|
||||
**FastAPI** проследит за тем, чтобы все выполнялось в правильном порядке.
|
||||
**FastAPI** проследит за тем, чтобы всё выполнялось в правильном порядке.
|
||||
|
||||
/// note | Технические детали
|
||||
|
||||
Это работает благодаря <a href="https://docs.python.org/3/library/contextlib.html" class="external-link" target="_blank">Контекстным менеджерам</a> в Python.
|
||||
Это работает благодаря <a href="https://docs.python.org/3/library/contextlib.html" class="external-link" target="_blank">менеджерам контекста</a> в Python.
|
||||
|
||||
**FastAPI** использует их внутренне для достижения этого.
|
||||
|
||||
///
|
||||
|
||||
**FastAPI** использует их "под капотом" с этой целью.
|
||||
## Зависимости с `yield` и `HTTPException` { #dependencies-with-yield-and-httpexception }
|
||||
|
||||
## Зависимости с `yield` и `HTTPException`
|
||||
Вы видели, что можно использовать зависимости с `yield` и иметь блоки `try`, которые пытаются выполнить некоторый код, а затем запускают код выхода в `finally`.
|
||||
|
||||
Вы видели, что можно использовать зависимости с `yield` совместно с блоком `try`, отлавливающие исключения.
|
||||
Также вы можете использовать `except`, чтобы поймать вызванное исключение и что-то с ним сделать.
|
||||
|
||||
Таким же образом вы можете поднять исключение `HTTPException` или что-то подобное в завершающем коде, после `yield`.
|
||||
|
||||
Код выхода в зависимостях с `yield` выполняется *после* отправки ответа, поэтому [Обработчик исключений](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} уже будет запущен. В коде выхода (после `yield`) нет ничего, перехватывающего исключения, брошенные вашими зависимостями.
|
||||
|
||||
Таким образом, если после `yield` возникает `HTTPException`, то стандартный (или любой пользовательский) обработчик исключений, который перехватывает `HTTPException` и возвращает ответ HTTP 400, уже не сможет перехватить это исключение.
|
||||
|
||||
Благодаря этому все, что установлено в зависимости (например, сеанс работы с БД), может быть использовано, например, фоновыми задачами.
|
||||
|
||||
Фоновые задачи выполняются *после* отправки ответа. Поэтому нет возможности поднять `HTTPException`, так как нет даже возможности изменить уже отправленный ответ.
|
||||
|
||||
Но если фоновая задача создает ошибку в БД, то, по крайней мере, можно сделать откат или чисто закрыть сессию в зависимости с помощью `yield`, а также, возможно, занести ошибку в журнал или сообщить о ней в удаленную систему отслеживания.
|
||||
|
||||
Если у вас есть код, который, как вы знаете, может вызвать исключение, сделайте самую обычную/"питонячью" вещь и добавьте блок `try` в этот участок кода.
|
||||
|
||||
Если у вас есть пользовательские исключения, которые вы хотите обрабатывать *до* возврата ответа и, возможно, модифицировать ответ, даже вызывая `HTTPException`, создайте [Cобственный обработчик исключений](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}.
|
||||
Например, вы можете <abbr title="«raise» дословно - «поднять», но «вызвать», «сгенерировать» или «выбросить» употребляется чаще">вызвать</abbr> другое исключение, например `HTTPException`.
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Вы все еще можете вызывать исключения, включая `HTTPException`, *до* `yield`. Но не после.
|
||||
Это довольно продвинутая техника, и в большинстве случаев она вам не понадобится, так как вы можете вызывать исключения (включая `HTTPException`) в остальном коде вашего приложения, например, в *функции-обработчике пути*.
|
||||
|
||||
Но если понадобится — возможность есть. 🤓
|
||||
|
||||
///
|
||||
|
||||
Последовательность выполнения примерно такая, как на этой схеме. Время течет сверху вниз. А каждый столбец - это одна из частей, взаимодействующих с кодом или выполняющих код.
|
||||
{* ../../docs_src/dependencies/tutorial008b_an_py39.py hl[18:22,31] *}
|
||||
|
||||
Если вы хотите перехватывать исключения и формировать на их основе пользовательский ответ, создайте [Пользовательский обработчик исключений](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}.
|
||||
|
||||
## Зависимости с `yield` и `except` { #dependencies-with-yield-and-except }
|
||||
|
||||
Если вы ловите исключение с помощью `except` в зависимости с `yield` и не вызываете его снова (или не вызываете новое исключение), FastAPI не сможет заметить, что было исключение — так же, как это происходит в обычном Python:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial008c_an_py39.py hl[15:16] *}
|
||||
|
||||
В этом случае клиент получит *HTTP 500 Internal Server Error*, как и должно быть, поскольку мы не вызываем `HTTPException` или что-то подобное, но на сервере **не будет никаких логов** или других указаний на то, какая была ошибка. 😱
|
||||
|
||||
### Всегда делайте `raise` в зависимостях с `yield` и `except` { #always-raise-in-dependencies-with-yield-and-except }
|
||||
|
||||
Если вы ловите исключение в зависимости с `yield`, то, если вы не вызываете другой `HTTPException` или что-то подобное, вам следует повторно вызвать исходное исключение.
|
||||
|
||||
Вы можете повторно вызвать то же самое исключение с помощью `raise`:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial008d_an_py39.py hl[17] *}
|
||||
|
||||
Теперь клиент получит тот же *HTTP 500 Internal Server Error*, но на сервере в логах будет наше пользовательское `InternalError`. 😎
|
||||
|
||||
## Выполнение зависимостей с `yield` { #execution-of-dependencies-with-yield }
|
||||
|
||||
Последовательность выполнения примерно такая, как на этой схеме. Время течёт сверху вниз. А каждый столбец — это одна из частей, взаимодействующих с кодом или выполняющих код.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
@@ -130,85 +144,56 @@ participant dep as Dep with yield
|
||||
participant operation as Path Operation
|
||||
participant tasks as Background tasks
|
||||
|
||||
Note over client,tasks: Can raise exception for dependency, handled after response is sent
|
||||
Note over client,operation: Can raise HTTPException and can change the response
|
||||
Note over client,operation: Can raise exceptions, including HTTPException
|
||||
client ->> dep: Start request
|
||||
Note over dep: Run code up to yield
|
||||
opt raise
|
||||
dep -->> handler: Raise HTTPException
|
||||
opt raise Exception
|
||||
dep -->> handler: Raise Exception
|
||||
handler -->> client: HTTP error response
|
||||
dep -->> dep: Raise other exception
|
||||
end
|
||||
dep ->> operation: Run dependency, e.g. DB session
|
||||
opt raise
|
||||
operation -->> dep: Raise HTTPException
|
||||
dep -->> handler: Auto forward exception
|
||||
operation -->> dep: Raise Exception (e.g. HTTPException)
|
||||
opt handle
|
||||
dep -->> dep: Can catch exception, raise a new HTTPException, raise other exception
|
||||
end
|
||||
handler -->> client: HTTP error response
|
||||
operation -->> dep: Raise other exception
|
||||
dep -->> handler: Auto forward exception
|
||||
end
|
||||
|
||||
operation ->> client: Return response to client
|
||||
Note over client,operation: Response is already sent, can't change it anymore
|
||||
opt Tasks
|
||||
operation -->> tasks: Send background tasks
|
||||
end
|
||||
opt Raise other exception
|
||||
tasks -->> dep: Raise other exception
|
||||
end
|
||||
Note over dep: After yield
|
||||
opt Handle other exception
|
||||
dep -->> dep: Handle exception, can't change response. E.g. close DB session.
|
||||
tasks -->> tasks: Handle exceptions in the background task code
|
||||
end
|
||||
```
|
||||
|
||||
/// info | Дополнительная информация
|
||||
|
||||
Клиенту будет отправлен только **один ответ**. Это может быть один из ответов об ошибке или это будет ответ от *операции пути*.
|
||||
Клиенту будет отправлен только **один ответ**. Это может быть один из ответов об ошибке или ответ от *операции пути*.
|
||||
|
||||
После отправки одного из этих ответов никакой другой ответ не может быть отправлен.
|
||||
После отправки одного из этих ответов никакой другой ответ отправить нельзя.
|
||||
|
||||
///
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
На этой диаграмме показано "HttpException", но вы также можете вызвать любое другое исключение, для которого вы создаете [Пользовательский обработчик исключений](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}.
|
||||
|
||||
Если вы создадите какое-либо исключение, оно будет передано зависимостям с yield, включая `HttpException`, а затем **снова** обработчикам исключений. Если для этого исключения нет обработчика исключений, то оно будет обработано внутренним "ServerErrorMiddleware" по умолчанию, возвращающим код состояния HTTP 500, чтобы уведомить клиента, что на сервере произошла ошибка.
|
||||
Если вы вызовете какое-либо исключение в коде из *функции-обработчика пути*, оно будет передано зависимостям с `yield`, включая `HTTPException`. В большинстве случаев вы захотите повторно вызвать то же самое исключение или новое из зависимости с `yield`, чтобы убедиться, что оно корректно обработано.
|
||||
|
||||
///
|
||||
|
||||
## Зависимости с `yield`, `HTTPException` и фоновыми задачами
|
||||
## Зависимости с `yield`, `HTTPException`, `except` и фоновыми задачами { #dependencies-with-yield-httpexception-except-and-background-tasks }
|
||||
|
||||
/// warning | Внимание
|
||||
Зависимости с `yield` со временем эволюционировали, чтобы покрыть разные сценарии и исправить некоторые проблемы.
|
||||
|
||||
Скорее всего, вам не нужны эти технические подробности, вы можете пропустить этот раздел и продолжить ниже.
|
||||
Если вы хотите посмотреть, что менялось в разных версиях FastAPI, вы можете прочитать об этом подробнее в продвинутом руководстве: [Продвинутые зависимости — зависимости с `yield`, `HTTPException`, `except` и фоновыми задачами](../../advanced/advanced-dependencies.md#dependencies-with-yield-httpexception-except-and-background-tasks){.internal-link target=_blank}.
|
||||
## Контекстные менеджеры { #context-managers }
|
||||
|
||||
Эти подробности полезны, главным образом, если вы использовали версию FastAPI до 0.106.0 и использовали ресурсы из зависимостей с `yield` в фоновых задачах.
|
||||
### Что такое «контекстные менеджеры» { #what-are-context-managers }
|
||||
|
||||
///
|
||||
|
||||
До версии FastAPI 0.106.0 вызывать исключения после `yield` было невозможно, код выхода в зависимостях с `yield` выполнялся *после* отправки ответа, поэтому [Обработчик Ошибок](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} уже был бы запущен.
|
||||
|
||||
Это было сделано главным образом для того, чтобы позволить использовать те же объекты, "отданные" зависимостями, внутри фоновых задач, поскольку код выхода будет выполняться после завершения фоновых задач.
|
||||
|
||||
Тем не менее, поскольку это означало бы ожидание ответа в сети, а также ненужное удержание ресурса в зависимости от доходности (например, соединение с базой данных), это было изменено в FastAPI 0.106.0.
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Кроме того, фоновая задача обычно представляет собой независимый набор логики, который должен обрабатываться отдельно, со своими собственными ресурсами (например, собственным подключением к базе данных).
|
||||
Таким образом, вы, вероятно, получите более чистый код.
|
||||
|
||||
///
|
||||
|
||||
Если вы полагались на это поведение, то теперь вам следует создавать ресурсы для фоновых задач внутри самой фоновой задачи, а внутри использовать только те данные, которые не зависят от ресурсов зависимостей с `yield`.
|
||||
|
||||
Например, вместо того чтобы использовать ту же сессию базы данных, вы создадите новую сессию базы данных внутри фоновой задачи и будете получать объекты из базы данных с помощью этой новой сессии. А затем, вместо того чтобы передавать объект из базы данных в качестве параметра в функцию фоновой задачи, вы передадите идентификатор этого объекта, а затем снова получите объект в функции фоновой задачи.
|
||||
|
||||
## Контекстные менеджеры
|
||||
|
||||
### Что такое "контекстные менеджеры"
|
||||
|
||||
"Контекстные менеджеры" - это любые объекты Python, которые можно использовать в операторе `with`.
|
||||
«Контекстные менеджеры» — это любые объекты Python, которые можно использовать в операторе `with`.
|
||||
|
||||
Например, <a href="https://docs.python.org/3/tutorial/inputoutput.html#reading-and-writing-files" class="external-link" target="_blank">можно использовать `with` для чтения файла</a>:
|
||||
|
||||
@@ -218,41 +203,41 @@ with open("./somefile.txt") as f:
|
||||
print(contents)
|
||||
```
|
||||
|
||||
Под капотом" open("./somefile.txt") создаёт объект называемый "контекстным менеджером".
|
||||
Под капотом вызов `open("./somefile.txt")` создаёт объект, называемый «контекстным менеджером».
|
||||
|
||||
Когда блок `with` завершается, он обязательно закрывает файл, даже если были исключения.
|
||||
|
||||
Когда вы создаете зависимость с помощью `yield`, **FastAPI** внутренне преобразует ее в контекстный менеджер и объединяет с некоторыми другими связанными инструментами.
|
||||
Когда вы создаёте зависимость с `yield`, **FastAPI** внутренне создаёт для неё менеджер контекста и сочетает его с некоторыми другими связанными инструментами.
|
||||
|
||||
### Использование менеджеров контекста в зависимостях с помощью `yield`
|
||||
### Использование менеджеров контекста в зависимостях с `yield` { #using-context-managers-in-dependencies-with-yield }
|
||||
|
||||
/// warning | Внимание
|
||||
|
||||
Это более или менее "продвинутая" идея.
|
||||
Это, более или менее, «продвинутая» идея.
|
||||
|
||||
Если вы только начинаете работать с **FastAPI**, то лучше пока пропустить этот пункт.
|
||||
|
||||
///
|
||||
|
||||
В Python для создания менеджеров контекста можно <a href="https://docs.python.org/3/reference/datamodel.html#context-managers" class="external-link" target="_blank">создать класс с двумя методами: `__enter__()` и `__exit__()`</a>.
|
||||
В Python можно создавать менеджеры контекста, <a href="https://docs.python.org/3/reference/datamodel.html#context-managers" class="external-link" target="_blank">создав класс с двумя методами: `__enter__()` и `__exit__()`</a>.
|
||||
|
||||
Вы также можете использовать их внутри зависимостей **FastAPI** с `yield`, используя операторы
|
||||
Их также можно использовать внутри зависимостей **FastAPI** с `yield`, применяя операторы
|
||||
`with` или `async with` внутри функции зависимости:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial010.py hl[1:9,13] *}
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Другой способ создания контекстного менеджера - с помощью:
|
||||
Другой способ создания менеджера контекста — с помощью:
|
||||
|
||||
* <a href="https://docs.python.org/3/library/contextlib.html#contextlib.contextmanager" class="external-link" target="_blank">`@contextlib.contextmanager`</a> или
|
||||
* <a href="https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager" class="external-link" target="_blank">`@contextlib.asynccontextmanager`</a>
|
||||
|
||||
используйте их для оформления функции с одним `yield`.
|
||||
оформив ими функцию с одним `yield`.
|
||||
|
||||
Это то, что **FastAPI** использует внутри себя для зависимостей с `yield`.
|
||||
Именно это **FastAPI** использует внутренне для зависимостей с `yield`.
|
||||
|
||||
Но использовать декораторы для зависимостей FastAPI не обязательно (да и не стоит).
|
||||
Но использовать эти декораторы для зависимостей FastAPI не обязательно (и не стоит).
|
||||
|
||||
FastAPI сделает это за вас на внутреннем уровне.
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
# Глобальные зависимости
|
||||
# Глобальные зависимости { #global-dependencies }
|
||||
|
||||
Для некоторых типов приложений может потребоваться добавить зависимости ко всему приложению.
|
||||
|
||||
Подобно тому, как вы можете [добавлять зависимости через параметр `dependencies` в *декораторах операций пути*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, вы можете добавлять зависимости сразу ко всему `FastAPI` приложению.
|
||||
Подобно тому, как вы можете [добавлять `dependencies` (зависимости) в *декораторах операций пути*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, вы можете добавлять зависимости сразу ко всему `FastAPI` приложению.
|
||||
|
||||
В этом случае они будут применяться ко всем *операциям пути* в приложении:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial012_an_py39.py hl[16] *}
|
||||
|
||||
Все способы [добавления зависимостей в *декораторах операций пути*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} по-прежнему применимы, но в данном случае зависимости применяются ко всем *операциям пути* приложения.
|
||||
Все способы [добавления `dependencies` (зависимостей) в *декораторах операций пути*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} по-прежнему применимы, но в данном случае зависимости применяются ко всем *операциям пути* приложения.
|
||||
|
||||
## Зависимости для групп *операций пути*
|
||||
## Зависимости для групп *операций пути* { #dependencies-for-groups-of-path-operations }
|
||||
|
||||
Позднее, читая о том, как структурировать более крупные [приложения, содержащие много файлов](../../tutorial/bigger-applications.md){.internal-link target=_blank}, вы узнаете, как объявить один параметр dependencies для целой группы *операций пути*.
|
||||
Позднее, читая о том, как структурировать более крупные [приложения, содержащие много файлов](../../tutorial/bigger-applications.md){.internal-link target=_blank}, вы узнаете, как объявить один параметр `dependencies` для целой группы *операций пути*.
|
||||
|
||||
@@ -1,91 +1,97 @@
|
||||
# Зависимости
|
||||
# Зависимости { #dependencies }
|
||||
|
||||
**FastAPI** имеет очень мощную и интуитивную систему **<abbr title="also known as components, resources, providers, services, injectables">Dependency Injection</abbr>**.
|
||||
**FastAPI** имеет очень мощную, но интуитивную систему **<abbr title="также известно как: компоненты, ресурсы, провайдеры, сервисы, внедряемые зависимости">Инъекция зависимостей</abbr>**.
|
||||
|
||||
Она проектировалась таким образом, чтобы быть простой в использовании и облегчить любому разработчику интеграцию других компонентов с **FastAPI**.
|
||||
Она спроектирована так, чтобы быть очень простой в использовании и облегчать любому разработчику интеграцию других компонентов с **FastAPI**.
|
||||
|
||||
## Что такое "Dependency Injection" (инъекция зависимости)
|
||||
## Что такое инъекция зависимостей («Dependency Injection») { #what-is-dependency-injection }
|
||||
|
||||
**"Dependency Injection"** в программировании означает, что у вашего кода (в данном случае, вашей *функции обработки пути*) есть способы объявить вещи, которые запрашиваются для работы и использования: "зависимости".
|
||||
В программировании **«Dependency Injection»** означает, что у вашего кода (в данном случае у ваших *функций обработки пути*) есть способ объявить вещи, которые требуются для его работы и использования: «зависимости».
|
||||
|
||||
И потом эта система (в нашем случае **FastAPI**) организует всё, что требуется, чтобы обеспечить ваш код этой зависимостью (сделать "инъекцию" зависимости).
|
||||
И затем эта система (в нашем случае **FastAPI**) позаботится о том, чтобы сделать всё необходимое для предоставления вашему коду этих зависимостей (сделать «инъекцию» зависимостей).
|
||||
|
||||
Это очень полезно, когда вам нужно:
|
||||
|
||||
* Обеспечить общую логику (один и тот же алгоритм снова и снова).
|
||||
* Общее соединение с базой данных.
|
||||
* Обеспечение безопасности, аутентификации, запроса роли и т.п.
|
||||
* И многое другое.
|
||||
* Разделять соединения с базой данных.
|
||||
* Обеспечить безопасность, аутентификацию, требования к ролям и т. п.
|
||||
* И многое другое...
|
||||
|
||||
Всё это минимизирует повторение кода.
|
||||
Всё это при минимизации повторения кода.
|
||||
|
||||
## Первые шаги
|
||||
## Первые шаги { #first-steps }
|
||||
|
||||
Давайте рассмотрим очень простой пример. Он настолько простой, что на данный момент почти бесполезный.
|
||||
Давайте рассмотрим очень простой пример. Он настолько простой, что пока не очень полезен.
|
||||
|
||||
Но таким способом мы можем сфокусироваться на том, как же всё таки работает система **Dependency Injection**.
|
||||
Но так мы сможем сосредоточиться на том, как работает система **Dependency Injection**.
|
||||
|
||||
### Создание зависимости или "зависимого"
|
||||
Давайте для начала сфокусируемся на зависимостях.
|
||||
### Создайте зависимость, или «dependable» (от чего что-то зависит) { #create-a-dependency-or-dependable }
|
||||
|
||||
Сначала сосредоточимся на зависимости.
|
||||
|
||||
Это просто функция, которая может принимать те же параметры, что и *функция обработки пути*:
|
||||
|
||||
Это просто функция, которая может принимать все те же параметры, что и *функции обработки пути*:
|
||||
{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[8:9] *}
|
||||
|
||||
**И всё.**
|
||||
И всё.
|
||||
|
||||
**2 строки.**
|
||||
|
||||
И теперь она той же формы и структуры, что и все ваши *функции обработки пути*.
|
||||
И она имеет ту же форму и структуру, что и все ваши *функции обработки пути*.
|
||||
|
||||
Вы можете думать об *функции обработки пути* как о функции без "декоратора" (без `@app.get("/some-path")`).
|
||||
Можно думать о ней как о *функции обработки пути* без «декоратора» (без `@app.get("/some-path")`).
|
||||
|
||||
И она может возвращать всё, что требуется.
|
||||
И она может возвращать что угодно.
|
||||
|
||||
В этом случае, эта зависимость ожидает:
|
||||
В этом случае эта зависимость ожидает:
|
||||
|
||||
* Необязательный query-параметр `q` с типом `str`
|
||||
* Необязательный query-параметр `skip` с типом `int`, и значением по умолчанию `0`
|
||||
* Необязательный query-параметр `limit` с типом `int`, и значением по умолчанию `100`
|
||||
* Необязательный query-параметр `q` типа `str`.
|
||||
* Необязательный query-параметр `skip` типа `int`, по умолчанию `0`.
|
||||
* Необязательный query-параметр `limit` типа `int`, по умолчанию `100`.
|
||||
|
||||
И в конце она возвращает `dict`, содержащий эти значения.
|
||||
А затем просто возвращает `dict`, содержащий эти значения.
|
||||
|
||||
/// info | Информация
|
||||
|
||||
**FastAPI** добавил поддержку для `Annotated` (и начал её рекомендовать) в версии 0.95.0.
|
||||
FastAPI добавил поддержку `Annotated` (и начал рекомендовать его использование) в версии 0.95.0.
|
||||
|
||||
Если у вас более старая версия, будут ошибки при попытке использовать `Annotated`.
|
||||
Если у вас более старая версия, вы получите ошибки при попытке использовать `Annotated`.
|
||||
|
||||
Убедитесь, что вы [Обновили FastAPI версию](../../deployment/versions.md#fastapi_2){.internal-link target=_blank} до, как минимум 0.95.1, перед тем как использовать `Annotated`.
|
||||
Убедитесь, что вы [обновили версию FastAPI](../../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} как минимум до 0.95.1, прежде чем использовать `Annotated`.
|
||||
|
||||
///
|
||||
|
||||
### Import `Depends`
|
||||
### Импорт `Depends` { #import-depends }
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[3] *}
|
||||
|
||||
### Объявите зависимость в "зависимом"
|
||||
### Объявите зависимость в «зависимом» { #declare-the-dependency-in-the-dependant }
|
||||
|
||||
Точно так же, как вы использовали `Body`, `Query` и т.д. с вашей *функцией обработки пути* для параметров, используйте `Depends` с новым параметром:
|
||||
Точно так же, как вы используете `Body`, `Query` и т. д. с параметрами вашей *функции обработки пути*, используйте `Depends` с новым параметром:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[13,18] *}
|
||||
|
||||
`Depends` работает немного иначе. Вы передаёте в `Depends` одиночный параметр, который будет похож на функцию.
|
||||
Хотя вы используете `Depends` в параметрах вашей функции так же, как `Body`, `Query` и т. д., `Depends` работает немного иначе.
|
||||
|
||||
Вы **не вызываете его** на месте (не добавляете скобочки в конце: 👎 *your_best_func()*👎), просто передаёте как параметр в `Depends()`.
|
||||
В `Depends` вы передаёте только один параметр.
|
||||
|
||||
И потом функция берёт параметры так же, как *функция обработки пути*.
|
||||
Этот параметр должен быть чем-то вроде функции.
|
||||
|
||||
Вы **не вызываете её** напрямую (не добавляйте круглые скобки в конце), просто передаёте её как параметр в `Depends()`.
|
||||
|
||||
И эта функция принимает параметры так же, как *функции обработки пути*.
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
В следующей главе вы увидите, какие другие вещи, помимо функций, можно использовать в качестве зависимостей.
|
||||
В следующей главе вы увидите, какие ещё «вещи», помимо функций, можно использовать в качестве зависимостей.
|
||||
|
||||
///
|
||||
|
||||
Каждый раз, когда новый запрос приходит, **FastAPI** позаботится о:
|
||||
Каждый раз, когда приходит новый запрос, **FastAPI** позаботится о:
|
||||
|
||||
* Вызове вашей зависимости ("зависимого") функции с корректными параметрами.
|
||||
* Вызове вашей зависимости («dependable») с корректными параметрами.
|
||||
* Получении результата из вашей функции.
|
||||
* Назначении результата в параметр в вашей *функции обработки пути*.
|
||||
* Присваивании этого результата параметру в вашей *функции обработки пути*.
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
@@ -98,121 +104,121 @@ common_parameters --> read_items
|
||||
common_parameters --> read_users
|
||||
```
|
||||
|
||||
Таким образом, вы пишете общий код один раз, и **FastAPI** позаботится о его вызове для ваших *операций с путями*.
|
||||
Таким образом, вы пишете общий код один раз, а **FastAPI** позаботится о его вызове для ваших *операций пути*.
|
||||
|
||||
/// check | Проверка
|
||||
|
||||
Обратите внимание, что вы не создаёте специальный класс и не передаёте его куда-то в **FastAPI** для регистрации, или что-то в этом роде.
|
||||
Обратите внимание, что вам не нужно создавать специальный класс и передавать его куда-то в **FastAPI**, чтобы «зарегистрировать» его или что-то подобное.
|
||||
|
||||
Вы просто передаёте это в `Depends`, и **FastAPI** знает, что делать дальше.
|
||||
Вы просто передаёте его в `Depends`, и **FastAPI** знает, что делать дальше.
|
||||
|
||||
///
|
||||
|
||||
## Объединяем с `Annotated` зависимостями
|
||||
## Использование зависимости с `Annotated` в нескольких местах { #share-annotated-dependencies }
|
||||
|
||||
В приведенном выше примере есть небольшое **повторение кода**.
|
||||
В приведённых выше примерах есть небольшое **повторение кода**.
|
||||
|
||||
Когда вам нужно использовать `common_parameters()` зависимость, вы должны написать весь параметр с аннотацией типов и `Depends()`:
|
||||
Когда вам нужно использовать зависимость `common_parameters()`, вы должны написать весь параметр с аннотацией типа и `Depends()`:
|
||||
|
||||
```Python
|
||||
commons: Annotated[dict, Depends(common_parameters)]
|
||||
```
|
||||
|
||||
Но потому что мы используем `Annotated`, мы можем хранить `Annotated` значение в переменной и использовать его в нескольких местах:
|
||||
Но поскольку мы используем `Annotated`, мы можем сохранить это значение `Annotated` в переменную и использовать его в нескольких местах:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial001_02_an_py310.py hl[12,16,21] *}
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Это стандартный синтаксис python и называется "type alias", это не особенность **FastAPI**.
|
||||
Это стандартный Python, это называется «type alias», и это не особенность **FastAPI**.
|
||||
|
||||
Но потому что **FastAPI** базируется на стандартах Python, включая `Annotated`, вы можете использовать этот трюк в вашем коде. 😎
|
||||
Но поскольку **FastAPI** основан на стандартах Python, включая `Annotated`, вы можете использовать этот трюк в своём коде. 😎
|
||||
|
||||
///
|
||||
|
||||
Зависимости продолжат работу как ожидалось, и **лучшая часть** в том, что **информация о типе будет сохранена**. Это означает, что ваш редактор кода будет корректно обрабатывать **автодополнения**, **встроенные ошибки** и так далее. То же самое относится и к инструментам, таким как `mypy`.
|
||||
Зависимости продолжат работать как ожидалось, и **лучшая часть** в том, что **информация о типах будет сохранена**, а значит, ваш редактор кода продолжит предоставлять **автозавершение**, **встроенные ошибки** и т.д. То же относится и к другим инструментам, таким как `mypy`.
|
||||
|
||||
Это очень полезно, когда вы интегрируете это в **большую кодовую базу**, используя **одинаковые зависимости** снова и снова во **многих** ***операциях пути***.
|
||||
Это особенно полезно, когда вы используете это в **большой кодовой базе**, где вы используете **одни и те же зависимости** снова и снова во **многих *операциях пути***.
|
||||
|
||||
## Использовать `async` или не `async`
|
||||
## Использовать `async` или не `async` { #to-async-or-not-to-async }
|
||||
|
||||
Для зависимостей, вызванных **FastAPI** (то же самое, что и ваши *функции обработки пути*), те же правила, что приняты для определения ваших функций.
|
||||
Поскольку зависимости также вызываются **FastAPI** (как и ваши *функции обработки пути*), применяются те же правила при определении ваших функций.
|
||||
|
||||
Вы можете использовать `async def` или обычное `def`.
|
||||
|
||||
Вы также можете объявить зависимости с `async def` внутри обычной `def` *функции обработки пути*, или `def` зависимости внутри `async def` *функции обработки пути*, и так далее.
|
||||
И вы можете объявлять зависимости с `async def` внутри обычных *функций обработки пути* `def`, или зависимости `def` внутри *функций обработки пути* `async def` и т. д.
|
||||
|
||||
Это всё не важно. **FastAPI** знает, что нужно сделать. 😎
|
||||
Это не важно. **FastAPI** знает, что делать.
|
||||
|
||||
/// note | Информация
|
||||
/// note | Примечание
|
||||
|
||||
Если вам эта тема не знакома, прочтите [Async: *"In a hurry?"*](../../async.md){.internal-link target=_blank} раздел о `async` и `await` в документации.
|
||||
Если вы не уверены, посмотрите раздел [Async: *"In a hurry?"*](../../async.md#in-a-hurry){.internal-link target=_blank} о `async` и `await` в документации.
|
||||
|
||||
///
|
||||
|
||||
## Интеграция с OpenAPI
|
||||
## Интеграция с OpenAPI { #integrated-with-openapi }
|
||||
|
||||
Все заявления о запросах, валидаторы, требования ваших зависимостей (и подзависимостей) будут интегрированы в соответствующую OpenAPI-схему.
|
||||
Все объявления запросов, проверки и требования ваших зависимостей (и подзависимостей) будут интегрированы в ту же схему OpenAPI.
|
||||
|
||||
В интерактивной документации будет вся информация по этим зависимостям тоже:
|
||||
Поэтому в интерактивной документации будет вся информация и из этих зависимостей:
|
||||
|
||||
<img src="/img/tutorial/dependencies/image01.png">
|
||||
|
||||
## Простое использование
|
||||
## Простое использование { #simple-usage }
|
||||
|
||||
Если вы посмотрите на фото, *функция обработки пути* объявляется каждый раз, когда вычисляется путь, и тогда **FastAPI** позаботится о вызове функции с корректными параметрами, извлекая информацию из запроса.
|
||||
Если посмотреть, *функции обработки пути* объявляются для использования всякий раз, когда *путь* и *операция* совпадают, и тогда **FastAPI** заботится о вызове функции с корректными параметрами, извлекая данные из запроса.
|
||||
|
||||
На самом деле, все (или большинство) веб-фреймворков работают по схожему сценарию.
|
||||
На самом деле все (или большинство) веб-фреймворков работают таким же образом.
|
||||
|
||||
Вы никогда не вызываете эти функции на месте. Их вызовет ваш фреймворк (в нашем случае, **FastAPI**).
|
||||
Вы никогда не вызываете эти функции напрямую. Их вызывает ваш фреймворк (в нашем случае **FastAPI**).
|
||||
|
||||
С системой Dependency Injection, вы можете сообщить **FastAPI**, что ваша *функция обработки пути* "зависит" от чего-то ещё, что должно быть извлечено перед вашей *функцией обработки пути*, и **FastAPI** позаботится об извлечении и инъекции результата.
|
||||
С системой **Dependency Injection** вы также можете сообщить **FastAPI**, что ваша *функция обработки пути* «зависит» от чего-то, что должно быть выполнено перед вашей *функцией обработки пути*, и **FastAPI** позаботится о его выполнении и «инъекции» результатов.
|
||||
|
||||
Другие распространённые термины для описания схожей идеи "dependency injection" являются:
|
||||
Другие распространённые термины для описания той же идеи «dependency injection»:
|
||||
|
||||
- ресурсность
|
||||
- доставка
|
||||
- сервисность
|
||||
- инъекция
|
||||
- компонентность
|
||||
* ресурсы
|
||||
* провайдеры
|
||||
* сервисы
|
||||
* внедряемые зависимости
|
||||
* компоненты
|
||||
|
||||
## **FastAPI** подключаемые модули
|
||||
## Плагины **FastAPI** { #fastapi-plug-ins }
|
||||
|
||||
Инъекции и модули могут быть построены с использованием системы **Dependency Injection**. Но на самом деле, **нет необходимости создавать новые модули**, просто используя зависимости, можно объявить бесконечное количество интеграций и взаимодействий, которые доступны вашей *функции обработки пути*.
|
||||
Интеграции и «плагины» могут быть построены с использованием системы **Dependency Injection**. Но на самом деле **нет необходимости создавать «плагины»**, так как, используя зависимости, можно объявить бесконечное количество интеграций и взаимодействий, которые становятся доступными вашим *функциям обработки пути*.
|
||||
|
||||
И зависимости могут быть созданы очень простым и интуитивным способом, что позволяет вам просто импортировать нужные пакеты Python и интегрировать их в API функции за пару строк.
|
||||
И зависимости можно создавать очень простым и интуитивным способом, который позволяет просто импортировать нужные пакеты Python и интегрировать их с вашими API-функциями в пару строк кода, *буквально*.
|
||||
|
||||
Вы увидите примеры этого в следующих главах о реляционных и NoSQL базах данных, безопасности и т.д.
|
||||
|
||||
## Совместимость с **FastAPI**
|
||||
## Совместимость с **FastAPI** { #fastapi-compatibility }
|
||||
|
||||
Простота Dependency Injection делает **FastAPI** совместимым с:
|
||||
Простота системы **Dependency Injection** делает **FastAPI** совместимым с:
|
||||
|
||||
- всеми реляционными базами данных
|
||||
- NoSQL базами данных
|
||||
- внешними пакетами
|
||||
- внешними API
|
||||
- системами авторизации, аутентификации
|
||||
- системами мониторинга использования API
|
||||
- системами ввода данных ответов
|
||||
- и так далее.
|
||||
* всеми реляционными базами данных
|
||||
* NoSQL базами данных
|
||||
* внешними пакетами
|
||||
* внешними API
|
||||
* системами аутентификации и авторизации
|
||||
* системами мониторинга использования API
|
||||
* системами инъекции данных в ответы
|
||||
* и т.д.
|
||||
|
||||
## Просто и сильно
|
||||
## Просто и мощно { #simple-and-powerful }
|
||||
|
||||
Хотя иерархическая система Dependency Injection очень проста для описания и использования, она по-прежнему очень мощная.
|
||||
Хотя иерархическая система dependency injection очень проста для определения и использования, она по-прежнему очень мощная.
|
||||
|
||||
Вы можете описывать зависимости в очередь, и они уже будут вызываться друг за другом.
|
||||
Вы можете определять зависимости, которые, в свою очередь, могут иметь собственные зависимости.
|
||||
|
||||
Когда иерархическое дерево построено, система **Dependency Injection** берет на себя решение всех зависимостей для вас (и их подзависимостей) и обеспечивает (инъектирует) результат на каждом шаге.
|
||||
В итоге строится иерархическое дерево зависимостей, и система **Dependency Injection** берёт на себя решение всех этих зависимостей (и их подзависимостей) и предоставляет (инъектирует) результаты на каждом шаге.
|
||||
|
||||
Например, у вас есть 4 API-эндпоинта (*операции пути*):
|
||||
|
||||
- `/items/public/`
|
||||
- `/items/private/`
|
||||
- `/users/{user_id}/activate`
|
||||
- `/items/pro/`
|
||||
* `/items/public/`
|
||||
* `/items/private/`
|
||||
* `/users/{user_id}/activate`
|
||||
* `/items/pro/`
|
||||
|
||||
Тогда вы можете требовать разные права для каждого из них, используя зависимости и подзависимости:
|
||||
тогда вы можете добавить разные требования к правам для каждого из них только с помощью зависимостей и подзависимостей:
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
@@ -237,8 +243,8 @@ admin_user --> activate_user
|
||||
paying_user --> pro_items
|
||||
```
|
||||
|
||||
## Интегрировано с **OpenAPI**
|
||||
## Интегрировано с **OpenAPI** { #integrated-with-openapi_1 }
|
||||
|
||||
Все эти зависимости, объявляя свои требования, также добавляют параметры, проверки и т.д. к вашим операциям *path*.
|
||||
Все эти зависимости, объявляя свои требования, также добавляют параметры, проверки и т.д. к вашим *операциям пути*.
|
||||
|
||||
**FastAPI** позаботится о добавлении всего этого в схему открытого API, чтобы это отображалось в системах интерактивной документации.
|
||||
**FastAPI** позаботится о добавлении всего этого в схему OpenAPI, чтобы это отображалось в системах интерактивной документации.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Подзависимости
|
||||
# Подзависимости { #sub-dependencies }
|
||||
|
||||
Вы можете создавать зависимости, которые имеют **подзависимости**.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
**FastAPI** сам займётся их управлением.
|
||||
|
||||
## Провайдер зависимости
|
||||
## Первая зависимость { #first-dependency-dependable }
|
||||
|
||||
Можно создать первую зависимость следующим образом:
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
|
||||
Это довольно просто (хотя и не очень полезно), но поможет нам сосредоточиться на том, как работают подзависимости.
|
||||
|
||||
## Вторая зависимость
|
||||
## Вторая зависимость, «зависимость» и «зависимая» { #second-dependency-dependable-and-dependant }
|
||||
|
||||
Затем можно создать еще одну функцию зависимости, которая в то же время содержит внутри себя первую зависимость (таким образом, она тоже является "зависимой"):
|
||||
Затем можно создать еще одну функцию зависимости, которая одновременно объявляет свою собственную зависимость (таким образом, она тоже является «зависимой»):
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial005_an_py310.py hl[13] *}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
* Она также объявляет необязательный куки-параметр `last_query` в виде строки.
|
||||
* Если пользователь не указал параметр `q` в запросе, то мы используем последний использованный запрос, который мы ранее сохранили в куки-параметре `last_query`.
|
||||
|
||||
## Использование зависимости
|
||||
## Использование зависимости { #use-the-dependency }
|
||||
|
||||
Затем мы можем использовать зависимость вместе с:
|
||||
|
||||
@@ -54,7 +54,7 @@ read_query["/items/"]
|
||||
query_extractor --> query_or_cookie_extractor --> read_query
|
||||
```
|
||||
|
||||
## Использование одной и той же зависимости несколько раз
|
||||
## Использование одной и той же зависимости несколько раз { #using-the-same-dependency-multiple-times }
|
||||
|
||||
Если одна из ваших зависимостей объявлена несколько раз для одной и той же *функции операции пути*, например, несколько зависимостей имеют общую подзависимость, **FastAPI** будет знать, что вызывать эту подзависимость нужно только один раз за запрос.
|
||||
|
||||
@@ -62,7 +62,7 @@ query_extractor --> query_or_cookie_extractor --> read_query
|
||||
|
||||
В расширенном сценарии, когда вы знаете, что вам нужно, чтобы зависимость вызывалась на каждом шаге (возможно, несколько раз) в одном и том же запросе, вместо использования "кэшированного" значения, вы можете установить параметр `use_cache=False` при использовании `Depends`:
|
||||
|
||||
//// tab | Python 3.6+
|
||||
//// tab | Python 3.8+
|
||||
|
||||
```Python hl_lines="1"
|
||||
async def needy_dependency(fresh_value: Annotated[str, Depends(get_value, use_cache=False)]):
|
||||
@@ -71,7 +71,7 @@ async def needy_dependency(fresh_value: Annotated[str, Depends(get_value, use_ca
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.6+ без Annotated
|
||||
//// tab | Python 3.8+ без Annotated
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
@@ -86,7 +86,7 @@ async def needy_dependency(fresh_value: str = Depends(get_value, use_cache=False
|
||||
|
||||
////
|
||||
|
||||
## Резюме
|
||||
## Резюме { #recap }
|
||||
|
||||
Помимо всех этих умных слов, используемых здесь, система внедрения зависимостей довольно проста.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# JSON кодировщик
|
||||
# JSON-совместимый кодировщик { #json-compatible-encoder }
|
||||
|
||||
В некоторых случаях может потребоваться преобразование типа данных (например, Pydantic-модели) в тип, совместимый с JSON (например, `dict`, `list` и т.д.).
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
Для этого **FastAPI** предоставляет функцию `jsonable_encoder()`.
|
||||
|
||||
## Использование `jsonable_encoder`
|
||||
## Использование `jsonable_encoder` { #using-the-jsonable-encoder }
|
||||
|
||||
Представим, что у вас есть база данных `fake_db`, которая принимает только JSON-совместимые данные.
|
||||
|
||||
@@ -14,21 +14,21 @@
|
||||
|
||||
В таком случае объект `datetime` следует преобразовать в строку соответствующую <a href="https://en.wikipedia.org/wiki/ISO_8601" class="external-link" target="_blank">формату ISO</a>.
|
||||
|
||||
Точно так же эта база данных не может принять Pydantic модель (объект с атрибутами), а только `dict`.
|
||||
Точно так же эта база данных не может принять Pydantic-модель (объект с атрибутами), а только `dict`.
|
||||
|
||||
Для этого можно использовать функцию `jsonable_encoder`.
|
||||
|
||||
Она принимает объект, например, модель Pydantic, и возвращает его версию, совместимую с JSON:
|
||||
Она принимает объект, например, Pydantic-модель, и возвращает его версию, совместимую с JSON:
|
||||
|
||||
{* ../../docs_src/encoder/tutorial001_py310.py hl[4,21] *}
|
||||
|
||||
В данном примере она преобразует Pydantic модель в `dict`, а `datetime` - в `str`.
|
||||
В данном примере она преобразует Pydantic-модель в `dict`, а `datetime` - в `str`.
|
||||
|
||||
Результатом её вызова является объект, который может быть закодирован с помощью функции из стандартной библиотеки Python – <a href="https://docs.python.org/3/library/json.html#json.dumps" class="external-link" target="_blank">`json.dumps()`</a>.
|
||||
|
||||
Функция не возвращает большой `str`, содержащий данные в формате JSON (в виде строки). Она возвращает стандартную структуру данных Python (например, `dict`) со значениями и подзначениями, которые совместимы с JSON.
|
||||
|
||||
/// note | Технические детали
|
||||
/// note | Примечание
|
||||
|
||||
`jsonable_encoder` фактически используется **FastAPI** внутри системы для преобразования данных. Однако он полезен и во многих других сценариях.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Дополнительные типы данных
|
||||
# Дополнительные типы данных { #extra-data-types }
|
||||
|
||||
До сих пор вы использовали простые типы данных, такие как:
|
||||
|
||||
@@ -9,15 +9,15 @@
|
||||
|
||||
Но вы также можете использовать и более сложные типы.
|
||||
|
||||
При этом у вас останутся те же возможности , что и до сих пор:
|
||||
При этом у вас останутся те же возможности, что и до сих пор:
|
||||
|
||||
* Отличная поддержка редактора.
|
||||
* Отличная поддержка редактора кода.
|
||||
* Преобразование данных из входящих запросов.
|
||||
* Преобразование данных для ответа.
|
||||
* Валидация данных.
|
||||
* Автоматическая аннотация и документация.
|
||||
|
||||
## Другие типы данных
|
||||
## Другие типы данных { #other-data-types }
|
||||
|
||||
Ниже перечислены некоторые из дополнительных типов данных, которые вы можете использовать:
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
* `datetime.timedelta`:
|
||||
* Встроенный в Python `datetime.timedelta`.
|
||||
* В запросах и ответах будет представлен в виде общего количества секунд типа `float`.
|
||||
* Pydantic также позволяет представить его как "Кодировку разницы во времени ISO 8601", <a href="https://docs.pydantic.dev/latest/concepts/serialization/#json_encoders" class="external-link" target="_blank">см. документацию для получения дополнительной информации</a>.
|
||||
* Pydantic также позволяет представить его как "Кодировку разницы во времени ISO 8601", <a href="https://docs.pydantic.dev/latest/concepts/serialization/#custom-serializers" class="external-link" target="_blank">см. документацию для получения дополнительной информации</a>.
|
||||
* `frozenset`:
|
||||
* В запросах и ответах обрабатывается так же, как и `set`:
|
||||
* В запросах будет прочитан список, исключены дубликаты и преобразован в `set`.
|
||||
@@ -49,14 +49,14 @@
|
||||
* `Decimal`:
|
||||
* Встроенный в Python `Decimal`.
|
||||
* В запросах и ответах обрабатывается так же, как и `float`.
|
||||
* Вы можете проверить все допустимые типы данных pydantic здесь: <a href="https://docs.pydantic.dev/latest/concepts/types/" class="external-link" target="_blank">Типы данных Pydantic</a>.
|
||||
* Вы можете проверить все допустимые типы данных Pydantic здесь: <a href="https://docs.pydantic.dev/latest/usage/types/types/" class="external-link" target="_blank">Типы данных Pydantic</a>.
|
||||
|
||||
## Пример
|
||||
## Пример { #example }
|
||||
|
||||
Вот пример *операции пути* с параметрами, который демонстрирует некоторые из вышеперечисленных типов.
|
||||
|
||||
{* ../../docs_src/extra_data_types/tutorial001.py hl[1,3,12:16] *}
|
||||
{* ../../docs_src/extra_data_types/tutorial001_an_py310.py hl[1,3,12:16] *}
|
||||
|
||||
Обратите внимание, что параметры внутри функции имеют свой естественный тип данных, и вы, например, можете выполнять обычные манипуляции с датами, такие как:
|
||||
|
||||
{* ../../docs_src/extra_data_types/tutorial001.py hl[18:19] *}
|
||||
{* ../../docs_src/extra_data_types/tutorial001_an_py310.py hl[18:19] *}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Дополнительные модели
|
||||
# Дополнительные модели { #extra-models }
|
||||
|
||||
В продолжение прошлого примера будет уже обычным делом иметь несколько связанных между собой моделей.
|
||||
|
||||
@@ -16,15 +16,23 @@
|
||||
|
||||
///
|
||||
|
||||
## Множественные модели
|
||||
## Множественные модели { #multiple-models }
|
||||
|
||||
Ниже изложена основная идея того, как могут выглядеть эти модели с полями для паролей, а также описаны места, где они используются:
|
||||
|
||||
{* ../../docs_src/extra_models/tutorial001_py310.py hl[7,9,14,20,22,27:28,31:33,38:39] *}
|
||||
|
||||
### Про `**user_in.dict()`
|
||||
/// info | Информация
|
||||
|
||||
#### `.dict()` из Pydantic
|
||||
В Pydantic v1 метод назывался `.dict()`, в Pydantic v2 он помечен как устаревший (но всё ещё поддерживается) и переименован в `.model_dump()`.
|
||||
|
||||
В примерах здесь используется `.dict()` для совместимости с Pydantic v1, но если вы используете Pydantic v2, следует использовать `.model_dump()`.
|
||||
|
||||
///
|
||||
|
||||
### Про `**user_in.dict()` { #about-user-in-dict }
|
||||
|
||||
#### `.dict()` из Pydantic { #pydantics-dict }
|
||||
|
||||
`user_in` - это Pydantic-модель класса `UserIn`.
|
||||
|
||||
@@ -61,7 +69,7 @@ print(user_dict)
|
||||
}
|
||||
```
|
||||
|
||||
#### Распаковка `dict`
|
||||
#### Распаковка `dict` { #unpacking-a-dict }
|
||||
|
||||
Если мы возьмём `dict` наподобие `user_dict` и передадим его в функцию (или класс), используя `**user_dict`, Python распакует его. Он передаст ключи и значения `user_dict` напрямую как аргументы типа ключ-значение.
|
||||
|
||||
@@ -93,7 +101,7 @@ UserInDB(
|
||||
)
|
||||
```
|
||||
|
||||
#### Pydantic-модель из содержимого другой модели
|
||||
#### Pydantic-модель из содержимого другой модели { #a-pydantic-model-from-the-contents-of-another }
|
||||
|
||||
Как в примере выше мы получили `user_dict` из `user_in.dict()`, этот код:
|
||||
|
||||
@@ -112,7 +120,7 @@ UserInDB(**user_in.dict())
|
||||
|
||||
Таким образом мы получаем Pydantic-модель на основе данных из другой Pydantic-модели.
|
||||
|
||||
#### Распаковка `dict` и дополнительные именованные аргументы
|
||||
#### Распаковка `dict` и дополнительные именованные аргументы { #unpacking-a-dict-and-extra-keywords }
|
||||
|
||||
И затем, если мы добавим дополнительный именованный аргумент `hashed_password=hashed_password` как здесь:
|
||||
|
||||
@@ -134,11 +142,11 @@ UserInDB(
|
||||
|
||||
/// warning | Предупреждение
|
||||
|
||||
Цель использованных в примере вспомогательных функций - не более чем демонстрация возможных операций с данными, но, конечно, они не обеспечивают настоящую безопасность.
|
||||
Вспомогательные функции `fake_password_hasher` и `fake_save_user` используются только для демонстрации возможного потока данных и, конечно, не обеспечивают настоящую безопасность.
|
||||
|
||||
///
|
||||
|
||||
## Сократите дублирование
|
||||
## Сократите дублирование { #reduce-duplication }
|
||||
|
||||
Сокращение дублирования кода - это одна из главных идей **FastAPI**.
|
||||
|
||||
@@ -156,13 +164,13 @@ UserInDB(
|
||||
|
||||
{* ../../docs_src/extra_models/tutorial002_py310.py hl[7,13:14,17:18,21:22] *}
|
||||
|
||||
## `Union` или `anyOf`
|
||||
## `Union` или `anyOf` { #union-or-anyof }
|
||||
|
||||
Вы можете определить ответ как `Union` из двух типов. Это означает, что ответ должен соответствовать одному из них.
|
||||
Вы можете определить ответ как `Union` из двух или более типов. Это означает, что ответ должен соответствовать одному из них.
|
||||
|
||||
Он будет определён в OpenAPI как `anyOf`.
|
||||
|
||||
Для этого используйте стандартные аннотации типов в Python <a href="https://docs.python.org/3/library/typing.html#typing.Union" class="external-link" target="_blank">`typing.Union`</a>:
|
||||
Для этого используйте стандартную аннотацию типов в Python <a href="https://docs.python.org/3/library/typing.html#typing.Union" class="external-link" target="_blank">`typing.Union`</a>:
|
||||
|
||||
/// note | Примечание
|
||||
|
||||
@@ -172,7 +180,7 @@ UserInDB(
|
||||
|
||||
{* ../../docs_src/extra_models/tutorial003_py310.py hl[1,14:15,18:20,33] *}
|
||||
|
||||
### `Union` в Python 3.10
|
||||
### `Union` в Python 3.10 { #union-in-python-3-10 }
|
||||
|
||||
В этом примере мы передаём `Union[PlaneItem, CarItem]` в качестве значения аргумента `response_model`.
|
||||
|
||||
@@ -186,7 +194,7 @@ some_variable: PlaneItem | CarItem
|
||||
|
||||
Но если мы помещаем его в `response_model=PlaneItem | CarItem` мы получим ошибку, потому что Python попытается произвести **некорректную операцию** между `PlaneItem` и `CarItem` вместо того, чтобы интерпретировать это как аннотацию типа.
|
||||
|
||||
## Список моделей
|
||||
## Список моделей { #list-of-models }
|
||||
|
||||
Таким же образом вы можете определять ответы как списки объектов.
|
||||
|
||||
@@ -194,7 +202,7 @@ some_variable: PlaneItem | CarItem
|
||||
|
||||
{* ../../docs_src/extra_models/tutorial004_py39.py hl[18] *}
|
||||
|
||||
## Ответ с произвольным `dict`
|
||||
## Ответ с произвольным `dict` { #response-with-arbitrary-dict }
|
||||
|
||||
Вы также можете определить ответ, используя произвольный одноуровневый `dict` и определяя только типы ключей и значений без использования Pydantic-моделей.
|
||||
|
||||
@@ -204,7 +212,7 @@ some_variable: PlaneItem | CarItem
|
||||
|
||||
{* ../../docs_src/extra_models/tutorial005_py39.py hl[6] *}
|
||||
|
||||
## Резюме
|
||||
## Резюме { #recap }
|
||||
|
||||
Используйте несколько Pydantic-моделей и свободно применяйте наследование для каждой из них.
|
||||
|
||||
|
||||
@@ -1,106 +1,122 @@
|
||||
# Первые шаги
|
||||
# Первые шаги { #first-steps }
|
||||
|
||||
Самый простой FastAPI файл может выглядеть так:
|
||||
Самый простой файл FastAPI может выглядеть так:
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001.py *}
|
||||
|
||||
Скопируйте в файл `main.py`.
|
||||
Скопируйте это в файл `main.py`.
|
||||
|
||||
Запустите сервер в режиме реального времени:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ uvicorn main:app --reload
|
||||
$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:solid">main.py</u>
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
<span style="color: green;">INFO</span>: Started reloader process [28720]
|
||||
<span style="color: green;">INFO</span>: Started server process [28722]
|
||||
<span style="color: green;">INFO</span>: Waiting for application startup.
|
||||
<span style="color: green;">INFO</span>: Application startup complete.
|
||||
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting development server 🚀
|
||||
|
||||
Searching for package file structure from directories
|
||||
with <font color="#3465A4">__init__.py</font> files
|
||||
Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> 🐍 main.py
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with
|
||||
the following code:
|
||||
|
||||
<u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u>
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font>
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000/docs</u></font>
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> tip </font></span> Running in development mode, for production use:
|
||||
<b>fastapi run</b>
|
||||
|
||||
Logs:
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Will watch for changes in these directories:
|
||||
<b>[</b><font color="#4E9A06">'/home/user/code/awesomeapp'</font><b>]</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font> <b>(</b>Press CTRL+C
|
||||
to quit<b>)</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started reloader process <b>[</b><font color="#34E2E2"><b>383138</b></font><b>]</b> using WatchFiles
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>383153</b></font><b>]</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
/// note | Технические детали
|
||||
|
||||
Команда `uvicorn main:app` обращается к:
|
||||
|
||||
* `main`: файл `main.py` (модуль Python).
|
||||
* `app`: объект, созданный внутри файла `main.py` в строке `app = FastAPI()`.
|
||||
* `--reload`: перезапускает сервер после изменения кода. Используйте только для разработки.
|
||||
|
||||
///
|
||||
|
||||
В окне вывода появится следующая строка:
|
||||
В выводе будет строка примерно такого вида:
|
||||
|
||||
```hl_lines="4"
|
||||
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
Эта строка показывает URL-адрес, по которому приложение доступно на локальной машине.
|
||||
Эта строка показывает URL, по которому ваше приложение доступно на локальной машине.
|
||||
|
||||
### Проверьте
|
||||
### Проверьте { #check-it }
|
||||
|
||||
Откройте браузер по адресу: <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>.
|
||||
|
||||
Вы увидите JSON-ответ следующего вида:
|
||||
Вы увидите JSON-ответ вида:
|
||||
|
||||
```JSON
|
||||
{"message": "Hello World"}
|
||||
```
|
||||
|
||||
### Интерактивная документация API
|
||||
### Интерактивная документация API { #interactive-api-docs }
|
||||
|
||||
Перейдите по адресу: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
|
||||
Теперь перейдите по адресу: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
|
||||
|
||||
Вы увидите автоматически сгенерированную, интерактивную документацию по API (предоставленную <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>):
|
||||
Вы увидите автоматически сгенерированную интерактивную документацию по API (предоставлено <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>):
|
||||
|
||||

|
||||
|
||||
### Альтернативная документация API
|
||||
### Альтернативная документация API { #alternative-api-docs }
|
||||
|
||||
Теперь перейдите по адресу <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>.
|
||||
И теперь перейдите по адресу <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>.
|
||||
|
||||
Вы увидите альтернативную автоматически сгенерированную документацию (предоставленную <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>):
|
||||
Вы увидите альтернативную автоматически сгенерированную документацию (предоставлено <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>):
|
||||
|
||||

|
||||
|
||||
### OpenAPI
|
||||
### OpenAPI { #openapi }
|
||||
|
||||
**FastAPI** генерирует "схему" всего API, используя стандарт **OpenAPI**.
|
||||
**FastAPI** генерирует «схему» всего вашего API, используя стандарт **OpenAPI** для описания API.
|
||||
|
||||
#### "Схема"
|
||||
#### «Схема» { #schema }
|
||||
|
||||
"Схема" - это определение или описание чего-либо. Не код, реализующий это, а только абстрактное описание.
|
||||
«Схема» — это определение или описание чего-либо. Не код, который это реализует, а только абстрактное описание.
|
||||
|
||||
#### API "схема"
|
||||
#### «Схема» API { #api-schema }
|
||||
|
||||
<a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> - это спецификация, которая определяет, как описывать схему API.
|
||||
В данном случае <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> — это спецификация, которая определяет, как описывать схему вашего API.
|
||||
|
||||
Определение схемы содержит пути (paths) API, их параметры и т.п.
|
||||
Это определение схемы включает пути вашего API, возможные параметры, которые они принимают, и т. п.
|
||||
|
||||
#### "Схема" данных
|
||||
#### «Схема» данных { #data-schema }
|
||||
|
||||
Термин "схема" также может относиться к формату или структуре некоторых данных, например, JSON.
|
||||
Термин «схема» также может относиться к форме некоторых данных, например, к содержимому JSON.
|
||||
|
||||
Тогда, подразумеваются атрибуты JSON, их типы данных и т.п.
|
||||
В таком случае это будут атрибуты JSON, их типы данных и т. п.
|
||||
|
||||
#### OpenAPI и JSON Schema
|
||||
#### OpenAPI и JSON Schema { #openapi-and-json-schema }
|
||||
|
||||
OpenAPI описывает схему API. Эта схема содержит определения (или "схемы") данных, отправляемых и получаемых API. Для описания структуры данных в JSON используется стандарт **JSON Schema**.
|
||||
OpenAPI определяет схему API для вашего API. И эта схема включает определения (или «схемы») данных, отправляемых и получаемых вашим API, с использованием стандарта **JSON Schema** для схем данных JSON.
|
||||
|
||||
#### Рассмотрим `openapi.json`
|
||||
#### Посмотрите `openapi.json` { #check-the-openapi-json }
|
||||
|
||||
Если Вас интересует, как выглядит исходная схема OpenAPI, то FastAPI автоматически генерирует JSON-схему со всеми описаниями API.
|
||||
Если вам интересно, как выглядит исходная схема OpenAPI, FastAPI автоматически генерирует JSON (схему) с описанием всего вашего API.
|
||||
|
||||
Можете посмотреть здесь: <a href="http://127.0.0.1:8000/openapi.json" class="external-link" target="_blank">http://127.0.0.1:8000/openapi.json</a>.
|
||||
Вы можете посмотреть её напрямую по адресу: <a href="http://127.0.0.1:8000/openapi.json" class="external-link" target="_blank">http://127.0.0.1:8000/openapi.json</a>.
|
||||
|
||||
Вы увидите примерно такой JSON:
|
||||
Вы увидите JSON, начинающийся примерно так:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {
|
||||
"title": "FastAPI",
|
||||
"version": "0.1.0"
|
||||
@@ -119,151 +135,123 @@ OpenAPI описывает схему API. Эта схема содержит о
|
||||
...
|
||||
```
|
||||
|
||||
#### Для чего нужен OpenAPI
|
||||
#### Для чего нужен OpenAPI { #what-is-openapi-for }
|
||||
|
||||
Схема OpenAPI является основой для обеих систем интерактивной документации.
|
||||
Схема OpenAPI является основой для обеих включённых систем интерактивной документации.
|
||||
|
||||
Существуют десятки альтернативных инструментов, основанных на OpenAPI. Вы можете легко добавить любой из них к **FastAPI** приложению.
|
||||
Есть десятки альтернатив, все основаны на OpenAPI. Вы можете легко добавить любую из них в ваше приложение, созданное с **FastAPI**.
|
||||
|
||||
Вы также можете использовать OpenAPI для автоматической генерации кода для клиентов, которые взаимодействуют с API. Например, для фронтенд-, мобильных или IoT-приложений.
|
||||
Вы также можете использовать её для автоматической генерации кода для клиентов, которые взаимодействуют с вашим API. Например, для фронтенд-, мобильных или IoT-приложений.
|
||||
|
||||
## Рассмотрим поэтапно
|
||||
## Рассмотрим поэтапно { #recap-step-by-step }
|
||||
|
||||
### Шаг 1: импортируйте `FastAPI`
|
||||
### Шаг 1: импортируйте `FastAPI` { #step-1-import-fastapi }
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001.py hl[1] *}
|
||||
|
||||
`FastAPI` это класс в Python, который предоставляет всю функциональность для API.
|
||||
`FastAPI` — это класс на Python, который предоставляет всю функциональность для вашего API.
|
||||
|
||||
/// note | Технические детали
|
||||
|
||||
`FastAPI` это класс, который наследуется непосредственно от `Starlette`.
|
||||
`FastAPI` — это класс, который напрямую наследуется от `Starlette`.
|
||||
|
||||
Вы можете использовать всю функциональность <a href="https://www.starlette.io/" class="external-link" target="_blank">Starlette</a> в `FastAPI`.
|
||||
Вы можете использовать весь функционал <a href="https://www.starlette.io/" class="external-link" target="_blank">Starlette</a> и в `FastAPI`.
|
||||
|
||||
///
|
||||
|
||||
### Шаг 2: создайте экземпляр `FastAPI`
|
||||
### Шаг 2: создайте экземпляр `FastAPI` { #step-2-create-a-fastapi-instance }
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001.py hl[3] *}
|
||||
|
||||
Переменная `app` является экземпляром класса `FastAPI`.
|
||||
Здесь переменная `app` будет экземпляром класса `FastAPI`.
|
||||
|
||||
Это единая точка входа для создания и взаимодействия с API.
|
||||
Это будет основная точка взаимодействия для создания всего вашего API.
|
||||
|
||||
Именно к этой переменной `app` обращается `uvicorn` в команде:
|
||||
### Шаг 3: создайте *операцию пути (path operation)* { #step-3-create-a-path-operation }
|
||||
|
||||
<div class="termy">
|
||||
#### Путь (path) { #path }
|
||||
|
||||
```console
|
||||
$ uvicorn main:app --reload
|
||||
Здесь «путь» — это последняя часть URL, начиная с первого символа `/`.
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
Если создать такое приложение:
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial002.py hl[3] *}
|
||||
|
||||
И поместить его в `main.py`, тогда вызов `uvicorn` будет таким:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ uvicorn main:my_awesome_api --reload
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
### Шаг 3: определите *операцию пути (path operation)*
|
||||
|
||||
#### Путь (path)
|
||||
|
||||
"Путь" это часть URL, после первого символа `/`, следующего за именем домена.
|
||||
|
||||
Для URL:
|
||||
Итак, в таком URL:
|
||||
|
||||
```
|
||||
https://example.com/items/foo
|
||||
```
|
||||
|
||||
...путь выглядит так:
|
||||
...путь будет:
|
||||
|
||||
```
|
||||
/items/foo
|
||||
```
|
||||
|
||||
/// info | Дополнительная иформация
|
||||
/// info | Информация
|
||||
|
||||
Термин "path" также часто называется "endpoint" или "route".
|
||||
«Путь» также часто называют «эндпоинт» или «маршрут».
|
||||
|
||||
///
|
||||
|
||||
При создании API, "путь" является основным способом разделения "задач" и "ресурсов".
|
||||
При создании API «путь» — это основной способ разделения «задач» и «ресурсов».
|
||||
|
||||
#### Операция (operation)
|
||||
#### Операция (operation) { #operation }
|
||||
|
||||
"Операция" это один из "методов" HTTP.
|
||||
«Операция» здесь — это один из HTTP-«методов».
|
||||
|
||||
Таких, как:
|
||||
Один из:
|
||||
|
||||
* `POST`
|
||||
* `GET`
|
||||
* `PUT`
|
||||
* `DELETE`
|
||||
|
||||
...и более экзотических:
|
||||
...и более экзотические:
|
||||
|
||||
* `OPTIONS`
|
||||
* `HEAD`
|
||||
* `PATCH`
|
||||
* `TRACE`
|
||||
|
||||
По протоколу HTTP можно обращаться к каждому пути, используя один (или несколько) из этих "методов".
|
||||
В протоколе HTTP можно обращаться к каждому пути, используя один (или несколько) из этих «методов».
|
||||
|
||||
---
|
||||
|
||||
При создании API принято использовать конкретные HTTP-методы для выполнения определенных действий.
|
||||
При создании API обычно используют конкретные HTTP-методы для выполнения конкретных действий.
|
||||
|
||||
Обычно используют:
|
||||
|
||||
* `POST`: создать данные.
|
||||
* `GET`: прочитать.
|
||||
* `PUT`: изменить (обновить).
|
||||
* `DELETE`: удалить.
|
||||
* `GET`: прочитать данные.
|
||||
* `PUT`: обновить данные.
|
||||
* `DELETE`: удалить данные.
|
||||
|
||||
В OpenAPI каждый HTTP метод называется "**операция**".
|
||||
Таким образом, в OpenAPI каждый HTTP-метод называется «операцией».
|
||||
|
||||
Мы также будем придерживаться этого термина.
|
||||
Мы тоже будем называть их «операциями».
|
||||
|
||||
#### Определите *декоратор операции пути (path operation decorator)*
|
||||
#### Определите *декоратор операции пути (path operation decorator)* { #define-a-path-operation-decorator }
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001.py hl[6] *}
|
||||
|
||||
Декоратор `@app.get("/")` указывает **FastAPI**, что функция, прямо под ним, отвечает за обработку запросов, поступающих по адресу:
|
||||
`@app.get("/")` сообщает **FastAPI**, что функция прямо под ним отвечает за обработку запросов, поступающих:
|
||||
|
||||
* путь `/`
|
||||
* использующих <abbr title="HTTP GET метод"><code>get</code> операцию</abbr>
|
||||
* по пути `/`
|
||||
* с использованием <abbr title="метод HTTP GET"><code>get</code> операции</abbr>
|
||||
|
||||
/// info | `@decorator` Дополнительная информация
|
||||
/// info | Информация о `@decorator`
|
||||
|
||||
Синтаксис `@something` в Python называется "декоратор".
|
||||
Синтаксис `@something` в Python называется «декоратор».
|
||||
|
||||
Вы помещаете его над функцией. Как красивую декоративную шляпу (думаю, что оттуда и происходит этот термин).
|
||||
Его размещают над функцией. Как красивая декоративная шляпа (кажется, отсюда и пошёл термин).
|
||||
|
||||
"Декоратор" принимает функцию ниже и выполняет с ней какое-то действие.
|
||||
«Декоратор» берёт функцию ниже и делает с ней что-то.
|
||||
|
||||
В нашем случае, этот декоратор сообщает **FastAPI**, что функция ниже соответствует **пути** `/` и **операции** `get`.
|
||||
В нашем случае этот декоратор сообщает **FastAPI**, что функция ниже соответствует **пути** `/` с **операцией** `get`.
|
||||
|
||||
Это и есть "**декоратор операции пути**".
|
||||
Это и есть «декоратор операции пути».
|
||||
|
||||
///
|
||||
|
||||
Можно также использовать операции:
|
||||
Можно также использовать другие операции:
|
||||
|
||||
* `@app.post()`
|
||||
* `@app.put()`
|
||||
@@ -278,58 +266,58 @@ https://example.com/items/foo
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Вы можете использовать каждую операцию (HTTP-метод) по своему усмотрению.
|
||||
Вы можете использовать каждый метод (HTTP-операцию) так, как считаете нужным.
|
||||
|
||||
**FastAPI** не навязывает определенного значения для каждого метода.
|
||||
**FastAPI** не навязывает какого-либо конкретного смысла.
|
||||
|
||||
Информация здесь представлена как рекомендация, а не требование.
|
||||
Эта информация дана как рекомендация, а не требование.
|
||||
|
||||
Например, при использовании GraphQL обычно все действия выполняются только с помощью POST операций.
|
||||
Например, при использовании GraphQL обычно все действия выполняются только с помощью POST-операций.
|
||||
|
||||
///
|
||||
|
||||
### Шаг 4: определите **функцию операции пути**
|
||||
### Шаг 4: определите **функцию операции пути** { #step-4-define-the-path-operation-function }
|
||||
|
||||
Вот "**функция операции пути**":
|
||||
Вот наша «функция операции пути»:
|
||||
|
||||
* **путь**: `/`.
|
||||
* **операция**: `get`.
|
||||
* **функция**: функция ниже "декоратора" (ниже `@app.get("/")`).
|
||||
* **функция**: функция ниже «декоратора» (ниже `@app.get("/")`).
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001.py hl[7] *}
|
||||
|
||||
Это обычная Python функция.
|
||||
Это функция на Python.
|
||||
|
||||
**FastAPI** будет вызывать её каждый раз при получении `GET` запроса к URL "`/`".
|
||||
**FastAPI** будет вызывать её каждый раз, когда получает запрос к URL «`/`» с операцией `GET`.
|
||||
|
||||
В данном случае это асинхронная функция.
|
||||
В данном случае это асинхронная (`async`) функция.
|
||||
|
||||
---
|
||||
|
||||
Вы также можете определить ее как обычную функцию вместо `async def`:
|
||||
Вы также можете определить её как обычную функцию вместо `async def`:
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial003.py hl[7] *}
|
||||
|
||||
/// note | Технические детали
|
||||
/// note | Примечание
|
||||
|
||||
Если не знаете в чём разница, посмотрите [Конкурентность: *"Нет времени?"*](../async.md#_1){.internal-link target=_blank}.
|
||||
Если вы не знаете, в чём разница, посмотрите [Асинхронность: *"Нет времени?"*](../async.md#in-a-hurry){.internal-link target=_blank}.
|
||||
|
||||
///
|
||||
|
||||
### Шаг 5: верните результат
|
||||
### Шаг 5: верните содержимое { #step-5-return-the-content }
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001.py hl[8] *}
|
||||
|
||||
Вы можете вернуть `dict`, `list`, отдельные значения `str`, `int` и т.д.
|
||||
|
||||
Также можно вернуть модели Pydantic (рассмотрим это позже).
|
||||
Также можно вернуть модели Pydantic (подробнее об этом позже).
|
||||
|
||||
Многие объекты и модели будут автоматически преобразованы в JSON (включая ORM). Пробуйте использовать другие объекты, которые предпочтительней для Вас, вероятно, они уже поддерживаются.
|
||||
Многие другие объекты и модели будут автоматически преобразованы в JSON (включая ORM и т. п.). Попробуйте использовать те, что вам привычнее, с высокой вероятностью они уже поддерживаются.
|
||||
|
||||
## Резюме
|
||||
## Резюме { #recap }
|
||||
|
||||
* Импортируем `FastAPI`.
|
||||
* Создаём экземпляр `app`.
|
||||
* Пишем **декоратор операции пути** (такой как `@app.get("/")`).
|
||||
* Пишем **функцию операции пути** (`def root(): ...`).
|
||||
* Запускаем сервер в режиме разработки (`uvicorn main:app --reload`).
|
||||
* Импортируйте `FastAPI`.
|
||||
* Создайте экземпляр `app`.
|
||||
* Напишите **декоратор операции пути**, например `@app.get("/")`.
|
||||
* Определите **функцию операции пути**; например, `def root(): ...`.
|
||||
* Запустите сервер разработки командой `fastapi dev`.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Обработка ошибок
|
||||
# Обработка ошибок { #handling-errors }
|
||||
|
||||
Существует множество ситуаций, когда необходимо сообщить об ошибке клиенту, использующему ваш API.
|
||||
|
||||
@@ -19,15 +19,15 @@
|
||||
|
||||
Помните ли ошибки **"404 Not Found "** (и шутки) ?
|
||||
|
||||
## Использование `HTTPException`
|
||||
## Использование `HTTPException` { #use-httpexception }
|
||||
|
||||
Для возврата клиенту HTTP-ответов с ошибками используется `HTTPException`.
|
||||
|
||||
### Импортируйте `HTTPException`
|
||||
### Импортируйте `HTTPException` { #import-httpexception }
|
||||
|
||||
{* ../../docs_src/handling_errors/tutorial001.py hl[1] *}
|
||||
|
||||
### Вызовите `HTTPException` в своем коде
|
||||
### Вызовите `HTTPException` в своем коде { #raise-an-httpexception-in-your-code }
|
||||
|
||||
`HTTPException` - это обычное исключение Python с дополнительными данными, актуальными для API.
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
|
||||
{* ../../docs_src/handling_errors/tutorial001.py hl[11] *}
|
||||
|
||||
### Возвращаемый ответ
|
||||
### Возвращаемый ответ { #the-resulting-response }
|
||||
|
||||
Если клиент запросит `http://example.com/items/foo` (`item_id` `"foo"`), то он получит статус-код 200 и ответ в формате JSON:
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
|
||||
///
|
||||
|
||||
## Добавление пользовательских заголовков
|
||||
## Добавление пользовательских заголовков { #add-custom-headers }
|
||||
|
||||
В некоторых ситуациях полезно иметь возможность добавлять пользовательские заголовки к ошибке HTTP. Например, для некоторых типов безопасности.
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
|
||||
{* ../../docs_src/handling_errors/tutorial002.py hl[14] *}
|
||||
|
||||
## Установка пользовательских обработчиков исключений
|
||||
## Установка пользовательских обработчиков исключений { #install-custom-exception-handlers }
|
||||
|
||||
Вы можете добавить пользовательские обработчики исключений с помощью <a href="https://www.starlette.io/exceptions/" class="external-link" target="_blank">то же самое исключение - утилиты от Starlette</a>.
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
|
||||
///
|
||||
|
||||
## Переопределение стандартных обработчиков исключений
|
||||
## Переопределение стандартных обработчиков исключений { #override-the-default-exception-handlers }
|
||||
|
||||
**FastAPI** имеет некоторые обработчики исключений по умолчанию.
|
||||
|
||||
@@ -117,7 +117,7 @@
|
||||
|
||||
Вы можете переопределить эти обработчики исключений на свои собственные.
|
||||
|
||||
### Переопределение исключений проверки запроса
|
||||
### Переопределение исключений проверки запроса { #override-request-validation-exceptions }
|
||||
|
||||
Когда запрос содержит недопустимые данные, **FastAPI** внутренне вызывает ошибку `RequestValidationError`.
|
||||
|
||||
@@ -154,7 +154,7 @@ path -> item_id
|
||||
value is not a valid integer (type=type_error.integer)
|
||||
```
|
||||
|
||||
#### `RequestValidationError` или `ValidationError`
|
||||
#### `RequestValidationError` или `ValidationError` { #requestvalidationerror-vs-validationerror }
|
||||
|
||||
/// warning | Внимание
|
||||
|
||||
@@ -172,7 +172,7 @@ path -> item_id
|
||||
|
||||
И пока вы не устраните ошибку, ваши клиенты/пользователи не должны иметь доступа к внутренней информации о ней, так как это может привести к уязвимости в системе безопасности.
|
||||
|
||||
### Переопределите обработчик ошибок `HTTPException`
|
||||
### Переопределите обработчик ошибок `HTTPException` { #override-the-httpexception-error-handler }
|
||||
|
||||
Аналогичным образом можно переопределить обработчик `HTTPException`.
|
||||
|
||||
@@ -188,7 +188,7 @@ path -> item_id
|
||||
|
||||
///
|
||||
|
||||
### Используйте тело `RequestValidationError`
|
||||
### Используйте тело `RequestValidationError` { #use-the-requestvalidationerror-body }
|
||||
|
||||
Ошибка `RequestValidationError` содержит полученное `тело` с недопустимыми данными.
|
||||
|
||||
@@ -226,21 +226,19 @@ path -> item_id
|
||||
}
|
||||
```
|
||||
|
||||
#### `HTTPException` в FastAPI или в Starlette
|
||||
#### `HTTPException` в FastAPI или в Starlette { #fastapis-httpexception-vs-starlettes-httpexception }
|
||||
|
||||
**FastAPI** имеет собственный `HTTPException`.
|
||||
|
||||
Класс ошибок **FastAPI** `HTTPException` наследует от класса ошибок Starlette `HTTPException`.
|
||||
|
||||
Единственное отличие заключается в том, что `HTTPException` от **FastAPI** позволяет добавлять заголовки, которые будут включены в ответ.
|
||||
|
||||
Он необходим/используется внутри системы для OAuth 2.0 и некоторых утилит безопасности.
|
||||
Единственное отличие состоит в том, что `HTTPException` в **FastAPI** принимает любые данные, пригодные для преобразования в JSON, в поле `detail`, тогда как `HTTPException` в Starlette принимает для него только строки.
|
||||
|
||||
Таким образом, вы можете продолжать вызывать `HTTPException` от **FastAPI** как обычно в своем коде.
|
||||
|
||||
Но когда вы регистрируете обработчик исключений, вы должны зарегистрировать его для `HTTPException` от Starlette.
|
||||
|
||||
Таким образом, если какая-либо часть внутреннего кода Starlette, расширение или плагин Starlette вызовет исключение Starlette `HTTPException`, ваш обработчик сможет перехватить и обработать его.
|
||||
Таким образом, если какая-либо часть внутреннего кодa Starlette, расширение или плагин Starlette вызовет исключение Starlette `HTTPException`, ваш обработчик сможет перехватить и обработать его.
|
||||
|
||||
В данном примере, чтобы иметь возможность использовать оба `HTTPException` в одном коде, исключения Starlette переименованы в `StarletteHTTPException`:
|
||||
|
||||
@@ -248,7 +246,7 @@ path -> item_id
|
||||
from starlette.exceptions import HTTPException as StarletteHTTPException
|
||||
```
|
||||
|
||||
### Переиспользование обработчиков исключений **FastAPI**
|
||||
### Переиспользование обработчиков исключений **FastAPI** { #reuse-fastapis-exception-handlers }
|
||||
|
||||
Если вы хотите использовать исключение вместе с теми же обработчиками исключений по умолчанию из **FastAPI**, вы можете импортировать и повторно использовать обработчики исключений по умолчанию из `fastapi.exception_handlers`:
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Модели Header-параметров
|
||||
# Модели Header-параметров { #header-parameter-models }
|
||||
|
||||
Если у вас есть группа связанных **header-параметров**, то вы можете объединить их в одну **Pydantic-модель**.
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
///
|
||||
|
||||
## Header-параметры в виде Pydantic-модели
|
||||
## Header-параметры в виде Pydantic-модели { #header-parameters-with-a-pydantic-model }
|
||||
|
||||
Объявите нужные **header-параметры** в **Pydantic-модели** и затем аннотируйте параметр как `Header`:
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
**FastAPI** **извлечёт** данные для **каждого поля** из **заголовков** запроса и выдаст заданную вами Pydantic-модель.
|
||||
|
||||
## Проверьте документацию
|
||||
## Проверьте документацию { #check-the-docs }
|
||||
|
||||
Вы можете посмотреть нужные header-параметры в графическом интерфейсе сгенерированной документации по пути `/docs`:
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<img src="/img/tutorial/header-param-models/image01.png">
|
||||
</div>
|
||||
|
||||
## Как запретить дополнительные заголовки
|
||||
## Как запретить дополнительные заголовки { #forbid-extra-headers }
|
||||
|
||||
В некоторых случаях (не особо часто встречающихся) вам может понадобиться **ограничить** заголовки, которые вы хотите получать.
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
}
|
||||
```
|
||||
|
||||
## Как отключить автоматическое преобразование подчеркиваний
|
||||
## Как отключить автоматическое преобразование подчеркиваний { #disable-convert-underscores }
|
||||
|
||||
Как и в случае с обычными заголовками, если у вас в именах параметров имеются символы подчеркивания, они **автоматически преобразовываются в дефис**.
|
||||
|
||||
@@ -67,6 +67,6 @@
|
||||
|
||||
///
|
||||
|
||||
## Резюме
|
||||
## Резюме { #summary }
|
||||
|
||||
Вы можете использовать **Pydantic-модели** для объявления **header-параметров** в **FastAPI**. 😎
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
# Header-параметры
|
||||
# Header-параметры { #header-parameters }
|
||||
|
||||
Вы можете определить параметры заголовка таким же образом, как вы определяете параметры `Query`, `Path` и `Cookie`.
|
||||
|
||||
## Импорт `Header`
|
||||
## Импорт `Header` { #import-header }
|
||||
|
||||
Сперва импортируйте `Header`:
|
||||
|
||||
{* ../../docs_src/header_params/tutorial001_an_py310.py hl[3] *}
|
||||
|
||||
## Объявление параметров `Header`
|
||||
## Объявление параметров `Header` { #declare-header-parameters }
|
||||
|
||||
Затем объявите параметры заголовка, используя ту же структуру, что и с `Path`, `Query` и `Cookie`.
|
||||
|
||||
@@ -24,13 +24,13 @@
|
||||
|
||||
///
|
||||
|
||||
/// info | Дополнительная информация
|
||||
/// info | Информация
|
||||
|
||||
Чтобы объявить заголовки, важно использовать `Header`, иначе параметры интерпретируются как query-параметры.
|
||||
|
||||
///
|
||||
|
||||
## Автоматическое преобразование
|
||||
## Автоматическое преобразование { #automatic-conversion }
|
||||
|
||||
`Header` обладает небольшой дополнительной функциональностью в дополнение к тому, что предоставляют `Path`, `Query` и `Cookie`.
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
|
||||
///
|
||||
|
||||
## Повторяющиеся заголовки
|
||||
## Повторяющиеся заголовки { #duplicate-headers }
|
||||
|
||||
Есть возможность получать несколько заголовков с одним и тем же именем, но разными значениями.
|
||||
|
||||
@@ -84,7 +84,7 @@ X-Token: bar
|
||||
}
|
||||
```
|
||||
|
||||
## Резюме
|
||||
## Резюме { #recap }
|
||||
|
||||
Объявляйте заголовки с помощью `Header`, используя тот же общий шаблон, как при `Query`, `Path` и `Cookie`.
|
||||
|
||||
|
||||
@@ -1,83 +1,95 @@
|
||||
# Учебник - Руководство пользователя
|
||||
# Учебник - Руководство пользователя { #tutorial-user-guide }
|
||||
|
||||
В этом руководстве шаг за шагом показано, как использовать **FastApi** с большинством его функций.
|
||||
В этом руководстве шаг за шагом показано, как использовать **FastAPI** с большинством его функций.
|
||||
|
||||
Каждый раздел постепенно основывается на предыдущих, но он структурирован по отдельным темам, так что вы можете перейти непосредственно к конкретной теме для решения ваших конкретных потребностей в API.
|
||||
Каждый раздел постепенно основывается на предыдущих, но структура разделяет темы, так что вы можете сразу перейти к нужной теме для решения ваших конкретных задач по API.
|
||||
|
||||
Он также создан для использования в качестве будущего справочника.
|
||||
Он также создан как справочник на будущее, чтобы вы могли вернуться и посмотреть именно то, что вам нужно.
|
||||
|
||||
Так что вы можете вернуться и посмотреть именно то, что вам нужно.
|
||||
## Запустите код { #run-the-code }
|
||||
|
||||
## Запустите код
|
||||
Все блоки кода можно копировать и использовать напрямую (это действительно протестированные файлы Python).
|
||||
|
||||
Все блоки кода можно копировать и использовать напрямую (на самом деле это проверенные файлы Python).
|
||||
|
||||
Чтобы запустить любой из примеров, скопируйте код в файл `main.py` и запустите `uvicorn` с параметрами:
|
||||
Чтобы запустить любой из примеров, скопируйте код в файл `main.py` и запустите `fastapi dev` с:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ uvicorn main:app --reload
|
||||
$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:solid">main.py</u>
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
<span style="color: green;">INFO</span>: Started reloader process [28720]
|
||||
<span style="color: green;">INFO</span>: Started server process [28722]
|
||||
<span style="color: green;">INFO</span>: Waiting for application startup.
|
||||
<span style="color: green;">INFO</span>: Application startup complete.
|
||||
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting development server 🚀
|
||||
|
||||
Searching for package file structure from directories
|
||||
with <font color="#3465A4">__init__.py</font> files
|
||||
Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> 🐍 main.py
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with
|
||||
the following code:
|
||||
|
||||
<u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u>
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font>
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000/docs</u></font>
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> tip </font></span> Running in development mode, for production use:
|
||||
<b>fastapi run</b>
|
||||
|
||||
Logs:
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Will watch for changes in these directories:
|
||||
<b>[</b><font color="#4E9A06">'/home/user/code/awesomeapp'</font><b>]</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font> <b>(</b>Press CTRL+C
|
||||
to quit<b>)</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started reloader process <b>[</b><font color="#34E2E2"><b>383138</b></font><b>]</b> using WatchFiles
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>383153</b></font><b>]</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
**НАСТОЯТЕЛЬНО рекомендуется**, чтобы вы написали или скопировали код, отредактировали его и запустили локально.
|
||||
**НАСТОЯТЕЛЬНО рекомендуется** написать или скопировать код, отредактировать его и запустить локально.
|
||||
|
||||
Использование кода в вашем редакторе — это то, что действительно показывает вам преимущества FastAPI, видя, как мало кода вам нужно написать, все проверки типов, автодополнение и т.д.
|
||||
Использование кода в вашем редакторе кода — это то, что действительно показывает преимущества FastAPI: вы увидите, как мало кода нужно написать, все проверки типов, автозавершение и т.д.
|
||||
|
||||
---
|
||||
|
||||
## Установка FastAPI
|
||||
## Установка FastAPI { #install-fastapi }
|
||||
|
||||
Первый шаг — установить FastAPI.
|
||||
|
||||
Для руководства вы, возможно, захотите установить его со всеми дополнительными зависимостями и функциями:
|
||||
Убедитесь, что вы создали [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активировали его, и затем **установите FastAPI**:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install "fastapi[all]"
|
||||
$ pip install "fastapi[standard]"
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
...это также включает `uvicorn`, который вы можете использовать в качестве сервера, который запускает ваш код.
|
||||
/// note | Примечание
|
||||
|
||||
/// note | Технические детали
|
||||
При установке с помощью `pip install "fastapi[standard]"` добавляются некоторые стандартные необязательные зависимости по умолчанию, включая `fastapi-cloud-cli`, который позволяет развернуть приложение на <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>.
|
||||
|
||||
Вы также можете установить его по частям.
|
||||
Если вы не хотите иметь эти необязательные зависимости, установите просто `pip install fastapi`.
|
||||
|
||||
Это то, что вы, вероятно, сделаете, когда захотите развернуть свое приложение в рабочей среде:
|
||||
|
||||
```
|
||||
pip install fastapi
|
||||
```
|
||||
|
||||
Также установите `uvicorn` для работы в качестве сервера:
|
||||
|
||||
```
|
||||
pip install "uvicorn[standard]"
|
||||
```
|
||||
|
||||
И то же самое для каждой из необязательных зависимостей, которые вы хотите использовать.
|
||||
Если вы хотите установить стандартные зависимости, но без `fastapi-cloud-cli`, установите `pip install "fastapi[standard-no-fastapi-cloud-cli]"`.
|
||||
|
||||
///
|
||||
|
||||
## Продвинутое руководство пользователя
|
||||
## Продвинутое руководство пользователя { #advanced-user-guide }
|
||||
|
||||
Существует также **Продвинутое руководство пользователя**, которое вы сможете прочитать после руководства **Учебник - Руководство пользователя**.
|
||||
Существует также **Продвинутое руководство пользователя**, которое вы сможете прочитать после **Учебник - Руководство пользователя**.
|
||||
|
||||
**Продвинутое руководство пользователя** основано на этом, использует те же концепции и учит вас некоторым дополнительным функциям.
|
||||
**Продвинутое руководство пользователя** основано на этом, использует те же концепции и обучает некоторым дополнительным функциям.
|
||||
|
||||
Но вы должны сначала прочитать **Учебник - Руководство пользователя** (то, что вы читаете прямо сейчас).
|
||||
Но сначала вам следует прочитать **Учебник - Руководство пользователя** (то, что вы читаете прямо сейчас).
|
||||
|
||||
Он разработан таким образом, что вы можете создать полноценное приложение, используя только **Учебник - Руководство пользователя**, а затем расширить его различными способами, в зависимости от ваших потребностей, используя некоторые дополнительные идеи из **Продвинутого руководства пользователя**.
|
||||
Оно спроектировано так, что вы можете создать полноценное приложение, используя только **Учебник - Руководство пользователя**, а затем расширить его различными способами, в зависимости от ваших потребностей, используя дополнительные идеи из **Продвинутого руководства пользователя**.
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
# URL-адреса метаданных и документации
|
||||
# URL-адреса метаданных и документации { #metadata-and-docs-urls }
|
||||
|
||||
Вы можете настроить несколько конфигураций метаданных в вашем **FastAPI** приложении.
|
||||
|
||||
## Метаданные для API
|
||||
## Метаданные для API { #metadata-for-api }
|
||||
|
||||
Вы можете задать следующие поля, которые используются в спецификации OpenAPI и в UI автоматической документации API:
|
||||
|
||||
| Параметр | Тип | Описание |
|
||||
|------------|--|-------------|
|
||||
|------------|------|-------------|
|
||||
| `title` | `str` | Заголовок API. |
|
||||
| `summary` | `str` | Краткое резюме API. <small>Доступно начиная с OpenAPI 3.1.0, FastAPI 0.99.0.</small> |
|
||||
| `description` | `str` | Краткое описание API. Может быть использован Markdown. |
|
||||
| `version` | `string` | Версия API. Версия вашего собственного приложения, а не OpenAPI. К примеру `2.5.0`. |
|
||||
| `terms_of_service` | `str` | Ссылка к условиям пользования API. Если указано, то это должен быть URL-адрес. |
|
||||
| `contact` | `dict` | Контактная информация для открытого API. Может содержать несколько полей. <details><summary>поля <code>contact</code></summary><table><thead><tr><th>Параметр</th><th>Тип</th><th>Описание</th></tr></thead><tbody><tr><td><code>name</code></td><td><code>str</code></td><td>Идентификационное имя контактного лица/организации.</td></tr><tr><td><code>url</code></td><td><code>str</code></td><td>URL указывающий на контактную информацию. ДОЛЖЕН быть в формате URL.</td></tr><tr><td><code>email</code></td><td><code>str</code></td><td>Email адрес контактного лица/организации. ДОЛЖЕН быть в формате email адреса.</td></tr></tbody></table></details> |
|
||||
| `license_info` | `dict` | Информация о лицензии открытого API. Может содержать несколько полей. <details><summary>поля <code>license_info</code></summary><table><thead><tr><th>Параметр</th><th>Тип</th><th>Описание</th></tr></thead><tbody><tr><td><code>name</code></td><td><code>str</code></td><td><strong>ОБЯЗАТЕЛЬНО</strong> (если установлен параметр <code>license_info</code>). Название лицензии, используемой для API</td></tr><tr><td><code>url</code></td><td><code>str</code></td><td>URL, указывающий на лицензию, используемую для API. ДОЛЖЕН быть в формате URL.</td></tr></tbody></table></details> |
|
||||
| `license_info` | `dict` | Информация о лицензии открытого API. Может содержать несколько полей. <details><summary>поля <code>license_info</code></summary><table><thead><tr><th>Параметр</th><th>Тип</th><th>Описание</th></tr></thead><tbody><tr><td><code>name</code></td><td><code>str</code></td><td><strong>ОБЯЗАТЕЛЬНО</strong> (если установлен параметр <code>license_info</code>). Название лицензии, используемой для API.</td></tr><tr><td><code>identifier</code></td><td><code>str</code></td><td>Выражение лицензии <a href="https://spdx.org/licenses/" class="external-link" target="_blank">SPDX</a> для API. Поле <code>identifier</code> взаимоисключающее с полем <code>url</code>. <small>Доступно начиная с OpenAPI 3.1.0, FastAPI 0.99.0.</small></td></tr><tr><td><code>url</code></td><td><code>str</code></td><td>URL, указывающий на лицензию, используемую для API. ДОЛЖЕН быть в формате URL.</td></tr></tbody></table></details> |
|
||||
|
||||
Вы можете задать их следующим образом:
|
||||
|
||||
{* ../../docs_src/metadata/tutorial001.py hl[3:16,19:31] *}
|
||||
{* ../../docs_src/metadata/tutorial001.py hl[3:16, 19:32] *}
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
@@ -25,11 +26,19 @@
|
||||
|
||||
///
|
||||
|
||||
С этой конфигурацией автоматическая документация API будут выглядеть так:
|
||||
С этой конфигурацией автоматическая документация API будет выглядеть так:
|
||||
|
||||
<img src="/img/tutorial/metadata/image01.png">
|
||||
|
||||
## Метаданные для тегов
|
||||
## Идентификатор лицензии { #license-identifier }
|
||||
|
||||
Начиная с OpenAPI 3.1.0 и FastAPI 0.99.0, вы также можете задать `license_info` с помощью `identifier` вместо `url`.
|
||||
|
||||
К примеру:
|
||||
|
||||
{* ../../docs_src/metadata/tutorial001_1.py hl[31] *}
|
||||
|
||||
## Метаданные для тегов { #metadata-for-tags }
|
||||
|
||||
Вы также можете добавить дополнительные метаданные для различных тегов, используемых для группировки ваших операций пути с помощью параметра `openapi_tags`.
|
||||
|
||||
@@ -43,7 +52,7 @@
|
||||
* `description`: `str`-значение с кратким описанием для внешней документации.
|
||||
* `url` (**обязательно**): `str`-значение с URL-адресом для внешней документации.
|
||||
|
||||
### Создание метаданных для тегов
|
||||
### Создание метаданных для тегов { #create-metadata-for-tags }
|
||||
|
||||
Давайте попробуем сделать это на примере с тегами для `users` и `items`.
|
||||
|
||||
@@ -59,30 +68,31 @@
|
||||
|
||||
///
|
||||
|
||||
### Используйте собственные теги
|
||||
### Используйте собственные теги { #use-your-tags }
|
||||
|
||||
Используйте параметр `tags` с вашими *операциями пути* (и `APIRouter`ами), чтобы присвоить им различные теги:
|
||||
|
||||
{* ../../docs_src/metadata/tutorial004.py hl[21,26] *}
|
||||
|
||||
/// info | Дополнительная информация
|
||||
|
||||
Узнайте больше о тегах в [Конфигурации операции пути](path-operation-configuration.md#_3){.internal-link target=_blank}.
|
||||
Узнайте больше о тегах в [Конфигурации операции пути](path-operation-configuration.md#tags){.internal-link target=_blank}.
|
||||
|
||||
///
|
||||
|
||||
### Проверьте документацию
|
||||
### Проверьте документацию { #check-the-docs }
|
||||
|
||||
Теперь, если вы проверите документацию, вы увидите всю дополнительную информацию:
|
||||
|
||||
<img src="/img/tutorial/metadata/image02.png">
|
||||
|
||||
### Порядок расположения тегов
|
||||
### Порядок расположения тегов { #order-of-tags }
|
||||
|
||||
Порядок расположения словарей метаданных для каждого тега определяет также порядок, отображаемый в документах UI
|
||||
Порядок расположения словарей метаданных для каждого тега определяет также порядок, отображаемый в UI документации.
|
||||
|
||||
К примеру, несмотря на то, что `users` будут идти после `items` в алфавитном порядке, они отображаются раньше, потому что мы добавляем свои метаданные в качестве первого словаря в списке.
|
||||
|
||||
## URL-адреса OpenAPI
|
||||
## URL-адрес OpenAPI { #openapi-url }
|
||||
|
||||
По умолчанию схема OpenAPI отображена по адресу `/openapi.json`.
|
||||
|
||||
@@ -92,11 +102,11 @@
|
||||
|
||||
{* ../../docs_src/metadata/tutorial002.py hl[3] *}
|
||||
|
||||
Если вы хотите отключить схему OpenAPI полностью, вы можете задать `openapi_url=None`, это также отключит пользовательские интерфейсы документации, которые его использует.
|
||||
Если вы хотите отключить схему OpenAPI полностью, вы можете задать `openapi_url=None`, это также отключит пользовательские интерфейсы документации, которые её используют.
|
||||
|
||||
## URL-адреса документации
|
||||
## URL-адреса документации { #docs-urls }
|
||||
|
||||
Вы можете изменить конфигурацию двух пользовательских интерфейсов документации, среди которых
|
||||
Вы можете изменить конфигурацию двух пользовательских интерфейсов документации, которые включены:
|
||||
|
||||
* **Swagger UI**: отображаемый по адресу `/docs`.
|
||||
* Вы можете задать его URL с помощью параметра `docs_url`.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Middleware (Промежуточный слой)
|
||||
# Middleware (Промежуточный слой) { #middleware }
|
||||
|
||||
Вы можете добавить промежуточный слой (middleware) в **FastAPI** приложение.
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
|
||||
Если у вас есть зависимости с `yield`, то код выхода (код после `yield`) будет выполняться *после* middleware.
|
||||
|
||||
Если у вас имеются некие фоновые задачи (см. документацию), то они будут запущены после middleware.
|
||||
Если были какие‑либо фоновые задачи (рассматриваются в разделе [Фоновые задачи](background-tasks.md){.internal-link target=_blank}, вы увидите это позже), они будут запущены *после* всех middleware.
|
||||
|
||||
///
|
||||
|
||||
## Создание middleware
|
||||
## Создание middleware { #create-a-middleware }
|
||||
|
||||
Для создания middleware используйте декоратор `@app.middleware("http")`.
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
|
||||
///
|
||||
|
||||
### До и после `response`
|
||||
### До и после `response` { #before-and-after-the-response }
|
||||
|
||||
Вы можете добавить код, использующий `request` до передачи его какой-либо *операции пути*.
|
||||
|
||||
@@ -67,8 +67,31 @@
|
||||
|
||||
///
|
||||
|
||||
## Другие middleware
|
||||
## Порядок выполнения нескольких middleware { #multiple-middleware-execution-order }
|
||||
|
||||
Когда вы добавляете несколько middleware с помощью декоратора `@app.middleware()` или метода `app.add_middleware()`, каждое новое middleware оборачивает приложение, формируя стек. Последнее добавленное middleware — самое внешнее (*outermost*), а первое — самое внутреннее (*innermost*).
|
||||
|
||||
На пути обработки запроса сначала выполняется самое внешнее middleware.
|
||||
|
||||
На пути формирования ответа оно выполняется последним.
|
||||
|
||||
Например:
|
||||
|
||||
```Python
|
||||
app.add_middleware(MiddlewareA)
|
||||
app.add_middleware(MiddlewareB)
|
||||
```
|
||||
|
||||
Это приводит к следующему порядку выполнения:
|
||||
|
||||
* **Запрос**: MiddlewareB → MiddlewareA → маршрут
|
||||
|
||||
* **Ответ**: маршрут → MiddlewareA → MiddlewareB
|
||||
|
||||
Такое стековое поведение обеспечивает предсказуемый и управляемый порядок выполнения middleware.
|
||||
|
||||
## Другие middleware { #other-middlewares }
|
||||
|
||||
О других middleware вы можете узнать больше в разделе [Advanced User Guide: Advanced Middleware](../advanced/middleware.md){.internal-link target=_blank}.
|
||||
|
||||
В следующем разделе вы можете прочитать, как настроить <abbr title="Cross-Origin Resource Sharing">CORS</abbr> с помощью middleware.
|
||||
В следующем разделе вы можете прочитать, как настроить <abbr title="Cross-Origin Resource Sharing – совместное использование ресурсов между источниками">CORS</abbr> с помощью middleware.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Конфигурация операций пути
|
||||
# Конфигурация операций пути { #path-operation-configuration }
|
||||
|
||||
Существует несколько параметров, которые вы можете передать вашему *декоратору операций пути* для его настройки.
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
///
|
||||
|
||||
## Коды состояния
|
||||
## Статус-код ответа { #response-status-code }
|
||||
|
||||
Вы можете определить (HTTP) `status_code`, который будет использован в ответах вашей *операции пути*.
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
{* ../../docs_src/path_operation_configuration/tutorial001_py310.py hl[1,15] *}
|
||||
|
||||
Этот код состояния будет использован в ответе и будет добавлен в схему OpenAPI.
|
||||
Этот статус-код будет использован в ответе и будет добавлен в схему OpenAPI.
|
||||
|
||||
/// note | Технические детали
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
///
|
||||
|
||||
## Теги
|
||||
## Теги { #tags }
|
||||
|
||||
Вы можете добавлять теги к вашим *операциям пути*, добавив параметр `tags` с `list` заполненным `str`-значениями (обычно в нём только одна строка):
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
|
||||
<img src="/img/tutorial/path-operation-configuration/image01.png">
|
||||
|
||||
### Теги с перечислениями
|
||||
### Теги с перечислениями { #tags-with-enums }
|
||||
|
||||
Если у вас большое приложение, вы можете прийти к необходимости добавить **несколько тегов**, и возможно, вы захотите убедиться в том, что всегда используете **один и тот же тег** для связанных *операций пути*.
|
||||
|
||||
@@ -48,13 +48,13 @@
|
||||
|
||||
{* ../../docs_src/path_operation_configuration/tutorial002b.py hl[1,8:10,13,18] *}
|
||||
|
||||
## Краткое и развёрнутое содержание
|
||||
## Краткое и развёрнутое содержание { #summary-and-description }
|
||||
|
||||
Вы можете добавить параметры `summary` и `description`:
|
||||
|
||||
{* ../../docs_src/path_operation_configuration/tutorial003_py310.py hl[18:19] *}
|
||||
|
||||
## Описание из строк документации
|
||||
## Описание из строк документации { #description-from-docstring }
|
||||
|
||||
Так как описания обычно длинные и содержат много строк, вы можете объявить описание *операции пути* в функции <abbr title="многострочный текст, первое выражение внутри функции (не присвоенный какой-либо переменной), используемый для документации">строки документации</abbr> и **FastAPI** прочитает её отсюда.
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
|
||||
<img src="/img/tutorial/path-operation-configuration/image02.png">
|
||||
|
||||
## Описание ответа
|
||||
## Описание ответа { #response-description }
|
||||
|
||||
Вы можете указать описание ответа с помощью параметра `response_description`:
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
|
||||
///
|
||||
|
||||
/// check | Технические детали
|
||||
/// check
|
||||
|
||||
OpenAPI указывает, что каждой *операции пути* необходимо описание ответа.
|
||||
|
||||
@@ -88,7 +88,7 @@ OpenAPI указывает, что каждой *операции пути* не
|
||||
|
||||
<img src="/img/tutorial/path-operation-configuration/image03.png">
|
||||
|
||||
## Обозначение *операции пути* как устаревшей
|
||||
## Обозначение *операции пути* как устаревшей { #deprecate-a-path-operation }
|
||||
|
||||
Если вам необходимо пометить *операцию пути* как <abbr title="устаревшее, не рекомендовано к использованию">устаревшую</abbr>, при этом не удаляя её, передайте параметр `deprecated`:
|
||||
|
||||
@@ -102,6 +102,6 @@ OpenAPI указывает, что каждой *операции пути* не
|
||||
|
||||
<img src="/img/tutorial/path-operation-configuration/image05.png">
|
||||
|
||||
## Резюме
|
||||
## Резюме { #recap }
|
||||
|
||||
Вы можете легко конфигурировать и добавлять метаданные в ваши *операции пути*, передавая параметры *декораторам операций пути*.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Path-параметры и валидация числовых данных
|
||||
# Path-параметры и валидация числовых данных { #path-parameters-and-numeric-validations }
|
||||
|
||||
Так же, как с помощью `Query` вы можете добавлять валидацию и метаданные для query-параметров, так и с помощью `Path` вы можете добавлять такую же валидацию и метаданные для path-параметров.
|
||||
|
||||
## Импорт Path
|
||||
## Импорт `Path` { #import-path }
|
||||
|
||||
Сначала импортируйте `Path` из `fastapi`, а также импортируйте `Annotated`:
|
||||
|
||||
@@ -14,11 +14,11 @@
|
||||
|
||||
Если вы используете более старую версию, вы столкнётесь с ошибками при попытке использовать `Annotated`.
|
||||
|
||||
Убедитесь, что вы [обновили версию FastAPI](../deployment/versions.md#fastapi_2){.internal-link target=_blank} как минимум до 0.95.1 перед тем, как использовать `Annotated`.
|
||||
Убедитесь, что вы [обновили версию FastAPI](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} как минимум до 0.95.1 перед тем, как использовать `Annotated`.
|
||||
|
||||
///
|
||||
|
||||
## Определите метаданные
|
||||
## Определите метаданные { #declare-metadata }
|
||||
|
||||
Вы можете указать все те же параметры, что и для `Query`.
|
||||
|
||||
@@ -28,15 +28,11 @@
|
||||
|
||||
/// note | Примечание
|
||||
|
||||
Path-параметр всегда является обязательным, поскольку он составляет часть пути.
|
||||
|
||||
Поэтому следует объявить его с помощью `...`, чтобы обозначить, что этот параметр обязательный.
|
||||
|
||||
Тем не менее, даже если вы объявите его как `None` или установите для него значение по умолчанию, это ни на что не повлияет и параметр останется обязательным.
|
||||
Path-параметр всегда является обязательным, поскольку он должен быть частью пути. Даже если вы объявите его как `None` или зададите значение по умолчанию, это ни на что не повлияет — параметр всё равно будет обязательным.
|
||||
|
||||
///
|
||||
|
||||
## Задайте нужный вам порядок параметров
|
||||
## Задайте нужный вам порядок параметров { #order-the-parameters-as-you-need }
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
@@ -58,25 +54,13 @@ Path-параметр всегда является обязательным, п
|
||||
|
||||
Поэтому вы можете определить функцию так:
|
||||
|
||||
//// tab | Python 3.8 без Annotated
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial002.py hl[7] *}
|
||||
|
||||
/// tip | Подсказка
|
||||
Но имейте в виду, что если вы используете `Annotated`, вы не столкнётесь с этой проблемой, так как вы не используете значения по умолчанию параметров функции для `Query()` или `Path()`.
|
||||
|
||||
Рекомендуется использовать версию с `Annotated` если возможно.
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py *}
|
||||
|
||||
///
|
||||
|
||||
```Python hl_lines="7"
|
||||
{!> ../../docs_src/path_params_numeric_validations/tutorial002.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
Но имейте в виду, что если вы используете `Annotated`, вы не столкнётесь с этой проблемой, так как вы не используете `Query()` или `Path()` в качестве значения по умолчанию для параметра функции.
|
||||
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py hl[10] *}
|
||||
|
||||
## Задайте нужный вам порядок параметров, полезные приёмы
|
||||
## Задайте нужный вам порядок параметров, полезные приёмы { #order-the-parameters-as-you-need-tricks }
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
@@ -97,25 +81,25 @@ Path-параметр всегда является обязательным, п
|
||||
|
||||
Передайте `*` в качестве первого параметра функции.
|
||||
|
||||
Python не будет ничего делать с `*`, но он будет знать, что все следующие параметры являются именованными аргументами (парами ключ-значение), также известными как <abbr title="From: K-ey W-ord Arg-uments"><code>kwargs</code></abbr>, даже если у них нет значений по умолчанию.
|
||||
Python не будет ничего делать с `*`, но он будет знать, что все следующие параметры являются именованными аргументами (парами ключ-значение), также известными как <abbr title="От: K-ey W-ord Arg-uments"><code>kwargs</code></abbr>, даже если у них нет значений по умолчанию.
|
||||
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial003.py hl[7] *}
|
||||
|
||||
### Лучше с `Annotated`
|
||||
### Лучше с `Annotated` { #better-with-annotated }
|
||||
|
||||
Имейте в виду, что если вы используете `Annotated`, то, поскольку вы не используете значений по умолчанию для параметров функции, то у вас не возникнет подобной проблемы и вам не придётся использовать `*`.
|
||||
Имейте в виду, что если вы используете `Annotated`, то, поскольку вы не используете значений по умолчанию для параметров функции, у вас не возникнет подобной проблемы и вам, вероятно, не придётся использовать `*`.
|
||||
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py39.py hl[10] *}
|
||||
|
||||
## Валидация числовых данных: больше или равно
|
||||
## Валидация числовых данных: больше или равно { #number-validations-greater-than-or-equal }
|
||||
|
||||
С помощью `Query` и `Path` (и других классов, которые мы разберём позже) вы можете добавлять ограничения для числовых данных.
|
||||
|
||||
В этом примере при указании `ge=1`, параметр `item_id` должен быть больше или равен `1` ("`g`reater than or `e`qual").
|
||||
В этом примере при указании `ge=1`, параметр `item_id` должен быть целым числом "`g`reater than or `e`qual" — больше или равно `1`.
|
||||
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py39.py hl[10] *}
|
||||
|
||||
## Валидация числовых данных: больше и меньше или равно
|
||||
## Валидация числовых данных: больше и меньше или равно { #number-validations-greater-than-and-less-than-or-equal }
|
||||
|
||||
То же самое применимо к:
|
||||
|
||||
@@ -124,19 +108,19 @@ Python не будет ничего делать с `*`, но он будет з
|
||||
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial005_an_py39.py hl[10] *}
|
||||
|
||||
## Валидация числовых данных: числа с плавающей точкой, больше и меньше
|
||||
## Валидация числовых данных: числа с плавающей точкой, больше и меньше { #number-validations-floats-greater-than-and-less-than }
|
||||
|
||||
Валидация также применима к значениям типа `float`.
|
||||
|
||||
В этом случае становится важной возможность добавить ограничение <abbr title="greater than"><code>gt</code></abbr>, вместо <abbr title="greater than or equal"><code>ge</code></abbr>, поскольку в таком случае вы можете, например, создать ограничение, чтобы значение было больше `0`, даже если оно меньше `1`.
|
||||
В этом случае становится важной возможность добавить ограничение <abbr title="greater than – больше чем"><code>gt</code></abbr>, вместо <abbr title="greater than or equal – больше или равно"><code>ge</code></abbr>, поскольку в таком случае вы можете, например, создать ограничение, чтобы значение было больше `0`, даже если оно меньше `1`.
|
||||
|
||||
Таким образом, `0.5` будет корректным значением. А `0.0` или `0` — нет.
|
||||
|
||||
То же самое справедливо и для <abbr title="less than"><code>lt</code></abbr>.
|
||||
То же самое справедливо и для <abbr title="less than – меньше чем"><code>lt</code></abbr>.
|
||||
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py39.py hl[13] *}
|
||||
|
||||
## Резюме
|
||||
## Резюме { #recap }
|
||||
|
||||
С помощью `Query`, `Path` (и других классов, которые мы пока не затронули) вы можете добавлять метаданные и строковую валидацию тем же способом, как и в главе [Query-параметры и валидация строк](query-params-str-validations.md){.internal-link target=_blank}.
|
||||
|
||||
@@ -149,7 +133,7 @@ Python не будет ничего делать с `*`, но он будет з
|
||||
|
||||
/// info | Информация
|
||||
|
||||
`Query`, `Path` и другие классы, которые мы разберём позже, являются наследниками общего класса `Param`.
|
||||
`Query`, `Path` и другие классы, которые вы разберёте позже, являются наследниками общего класса `Param`.
|
||||
|
||||
Все они используют те же параметры для дополнительной валидации и метаданных, которые вы видели ранее.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Path-параметры
|
||||
# Path-параметры { #path-parameters }
|
||||
|
||||
Вы можете определить "параметры" или "переменные" пути, используя синтаксис форматированных строк Python:
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
{"item_id":"foo"}
|
||||
```
|
||||
|
||||
## Параметры пути с типами
|
||||
## Параметры пути с типами { #path-parameters-with-types }
|
||||
|
||||
Вы можете объявить тип параметра пути в функции, используя стандартные аннотации типов Python.
|
||||
Вы можете объявить тип параметра пути в функции, используя стандартные аннотации типов Python:
|
||||
|
||||
{* ../../docs_src/path_params/tutorial002.py hl[7] *}
|
||||
|
||||
@@ -22,11 +22,11 @@
|
||||
|
||||
/// check | Заметка
|
||||
|
||||
Это обеспечит поддержку редактора внутри функции (проверка ошибок, автодополнение и т.п.).
|
||||
Это обеспечит поддержку редактора кода внутри функции (проверка ошибок, автозавершение и т.п.).
|
||||
|
||||
///
|
||||
|
||||
## <abbr title="Или сериализация, парсинг">Преобразование</abbr> данных
|
||||
## <abbr title="также известное как: сериализация, парсинг, маршаллинг">Преобразование</abbr> данных { #data-conversion }
|
||||
|
||||
Если запустите этот пример и перейдёте по адресу: <a href="http://127.0.0.1:8000/items/3" class="external-link" target="_blank">http://127.0.0.1:8000/items/3</a>, то увидите ответ:
|
||||
|
||||
@@ -38,44 +38,45 @@
|
||||
|
||||
Обратите внимание на значение `3`, которое получила (и вернула) функция. Это целочисленный Python `int`, а не строка `"3"`.
|
||||
|
||||
Используя определения типов, **FastAPI** выполняет автоматический <abbr title="преобразование строк из HTTP-запроса в типы данных Python">"парсинг"</abbr> запросов.
|
||||
Используя такое объявление типов, **FastAPI** выполняет автоматический <abbr title="преобразование строк из HTTP-запроса в типы данных Python">"парсинг"</abbr> запросов.
|
||||
|
||||
///
|
||||
|
||||
## <abbr title="Или валидация">Проверка</abbr> данных
|
||||
## Валидация данных { #data-validation }
|
||||
|
||||
Если откроете браузер по адресу <a href="http://127.0.0.1:8000/items/foo" class="external-link" target="_blank">http://127.0.0.1:8000/items/foo</a>, то увидите интересную HTTP-ошибку:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": [
|
||||
"path",
|
||||
"item_id"
|
||||
],
|
||||
"msg": "value is not a valid integer",
|
||||
"type": "type_error.integer"
|
||||
}
|
||||
]
|
||||
"detail": [
|
||||
{
|
||||
"type": "int_parsing",
|
||||
"loc": [
|
||||
"path",
|
||||
"item_id"
|
||||
],
|
||||
"msg": "Input should be a valid integer, unable to parse string as an integer",
|
||||
"input": "foo"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
из-за того, что параметр пути `item_id` имеет значение `"foo"`, которое не является типом `int`.
|
||||
из-за того, что параметр пути `item_id` имеет значение `"foo"`, которое не является типом `int`.
|
||||
|
||||
Та же ошибка возникнет, если вместо `int` передать `float` , например: <a href="http://127.0.0.1:8000/items/4.2" class="external-link" target="_blank">http://127.0.0.1:8000/items/4.2</a>
|
||||
Та же ошибка возникнет, если вместо `int` передать `float`, например: <a href="http://127.0.0.1:8000/items/4.2" class="external-link" target="_blank">http://127.0.0.1:8000/items/4.2</a>
|
||||
|
||||
/// check | Заметка
|
||||
|
||||
**FastAPI** обеспечивает проверку типов, используя всё те же определения типов.
|
||||
**FastAPI** обеспечивает валидацию данных, используя всё те же определения типов.
|
||||
|
||||
Обратите внимание, что в тексте ошибки явно указано место не прошедшее проверку.
|
||||
Обратите внимание, что в тексте ошибки явно указано место, не прошедшее проверку.
|
||||
|
||||
Это очень полезно при разработке и отладке кода, который взаимодействует с API.
|
||||
|
||||
///
|
||||
|
||||
## Документация
|
||||
## Документация { #documentation }
|
||||
|
||||
И теперь, когда откроете браузер по адресу: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>, то увидите вот такую автоматически сгенерированную документацию API:
|
||||
|
||||
@@ -89,17 +90,17 @@
|
||||
|
||||
///
|
||||
|
||||
## Преимущества стандартизации, альтернативная документация
|
||||
## Преимущества стандартизации, альтернативная документация { #standards-based-benefits-alternative-documentation }
|
||||
|
||||
Поскольку сгенерированная схема соответствует стандарту <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md" class="external-link" target="_blank">OpenAPI</a>, её можно использовать со множеством совместимых инструментов.
|
||||
Поскольку сгенерированная схема соответствует стандарту <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md" class="external-link" target="_blank">OpenAPI</a>, её можно использовать со множеством совместимых инструментов.
|
||||
|
||||
Именно поэтому, FastAPI сам предоставляет альтернативную документацию API (используя ReDoc), которую можно получить по адресу: <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>.
|
||||
Именно поэтому, **FastAPI** сам предоставляет альтернативную документацию API (используя ReDoc), которую можно получить по адресу: <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>.
|
||||
|
||||
<img src="/img/tutorial/path-params/image02.png">
|
||||
|
||||
По той же причине, есть множество совместимых инструментов, включая инструменты генерации кода для многих языков.
|
||||
|
||||
## Pydantic
|
||||
## Pydantic { #pydantic }
|
||||
|
||||
Вся проверка данных выполняется под капотом с помощью <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a>. Поэтому вы можете быть уверены в качестве обработки данных.
|
||||
|
||||
@@ -107,7 +108,7 @@
|
||||
|
||||
Некоторые из них рассматриваются в следующих главах данного руководства.
|
||||
|
||||
## Порядок имеет значение
|
||||
## Порядок имеет значение { #order-matters }
|
||||
|
||||
При создании *операций пути* можно столкнуться с ситуацией, когда путь является фиксированным.
|
||||
|
||||
@@ -117,7 +118,6 @@
|
||||
|
||||
Поскольку *операции пути* выполняются в порядке их объявления, необходимо, чтобы путь для `/users/me` был объявлен раньше, чем путь для `/users/{user_id}`:
|
||||
|
||||
|
||||
{* ../../docs_src/path_params/tutorial003.py hl[6,11] *}
|
||||
|
||||
Иначе путь для `/users/{user_id}` также будет соответствовать `/users/me`, "подразумевая", что он получает параметр `user_id` со значением `"me"`.
|
||||
@@ -128,11 +128,11 @@
|
||||
|
||||
Первый будет выполняться всегда, так как путь совпадает первым.
|
||||
|
||||
## Предопределенные значения
|
||||
## Предопределенные значения { #predefined-values }
|
||||
|
||||
Что если нам нужно заранее определить допустимые *параметры пути*, которые *операция пути* может принимать? В таком случае можно использовать стандартное перечисление <abbr title="Enumeration">`Enum`</abbr> Python.
|
||||
|
||||
### Создание класса `Enum`
|
||||
### Создание класса `Enum` { #create-an-enum-class }
|
||||
|
||||
Импортируйте `Enum` и создайте подкласс, который наследуется от `str` и `Enum`.
|
||||
|
||||
@@ -150,33 +150,33 @@
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Если интересно, то "AlexNet", "ResNet" и "LeNet" - это названия <abbr title="Технически, это архитектуры моделей глубокого обучения">моделей</abbr> машинного обучения.
|
||||
Если интересно, то "AlexNet", "ResNet" и "LeNet" - это названия <abbr title="Технически, архитектуры моделей глубокого обучения">моделей</abbr> Машинного обучения.
|
||||
|
||||
///
|
||||
|
||||
### Определение *параметра пути*
|
||||
### Определение *параметра пути* { #declare-a-path-parameter }
|
||||
|
||||
Определите *параметр пути*, используя в аннотации типа класс перечисления (`ModelName`), созданный ранее:
|
||||
|
||||
{* ../../docs_src/path_params/tutorial005.py hl[16] *}
|
||||
|
||||
### Проверьте документацию
|
||||
### Проверьте документацию { #check-the-docs }
|
||||
|
||||
Поскольку доступные значения *параметра пути* определены заранее, интерактивная документация может наглядно их отображать:
|
||||
|
||||
<img src="/img/tutorial/path-params/image03.png">
|
||||
|
||||
### Работа с *перечислениями* в Python
|
||||
### Работа с *перечислениями* в Python { #working-with-python-enumerations }
|
||||
|
||||
Значение *параметра пути* будет *элементом перечисления*.
|
||||
|
||||
#### Сравнение *элементов перечисления*
|
||||
#### Сравнение *элементов перечисления* { #compare-enumeration-members }
|
||||
|
||||
Вы можете сравнить это значение с *элементом перечисления* класса `ModelName`:
|
||||
|
||||
{* ../../docs_src/path_params/tutorial005.py hl[17] *}
|
||||
|
||||
#### Получение *значения перечисления*
|
||||
#### Получение *значения перечисления* { #get-the-enumeration-value }
|
||||
|
||||
Можно получить фактическое значение (в данном случае - `str`) с помощью `model_name.value` или в общем случае `your_enum_member.value`:
|
||||
|
||||
@@ -188,7 +188,7 @@
|
||||
|
||||
///
|
||||
|
||||
#### Возврат *элементов перечисления*
|
||||
#### Возврат *элементов перечисления* { #return-enumeration-members }
|
||||
|
||||
Из *операции пути* можно вернуть *элементы перечисления*, даже вложенные в тело JSON (например в `dict`).
|
||||
|
||||
@@ -204,7 +204,7 @@
|
||||
}
|
||||
```
|
||||
|
||||
## Path-параметры, содержащие пути
|
||||
## Path-параметры, содержащие пути { #path-parameters-containing-paths }
|
||||
|
||||
Предположим, что есть *операция пути* с путем `/files/{file_path}`.
|
||||
|
||||
@@ -212,7 +212,7 @@
|
||||
|
||||
Тогда URL для этого файла будет такой: `/files/home/johndoe/myfile.txt`.
|
||||
|
||||
### Поддержка OpenAPI
|
||||
### Поддержка OpenAPI { #openapi-support }
|
||||
|
||||
OpenAPI не поддерживает способов объявления *параметра пути*, содержащего внутри *путь*, так как это может привести к сценариям, которые сложно определять и тестировать.
|
||||
|
||||
@@ -220,7 +220,7 @@ OpenAPI не поддерживает способов объявления *п
|
||||
|
||||
Документация по-прежнему будет работать, хотя и не добавит никакой информации о том, что параметр должен содержать путь.
|
||||
|
||||
### Конвертер пути
|
||||
### Конвертер пути { #path-convertor }
|
||||
|
||||
Благодаря одной из опций Starlette, можете объявить *параметр пути*, содержащий *путь*, используя URL вроде:
|
||||
|
||||
@@ -242,13 +242,14 @@ OpenAPI не поддерживает способов объявления *п
|
||||
|
||||
///
|
||||
|
||||
## Резюме
|
||||
## Резюме { #recap }
|
||||
|
||||
Используя **FastAPI** вместе со стандартными объявлениями типов Python (короткими и интуитивно понятными), вы получаете:
|
||||
|
||||
* Поддержку редактора (проверку ошибок, автозаполнение и т.п.)
|
||||
* Поддержку редактора кода (проверку ошибок, автозавершение и т.п.)
|
||||
* "<abbr title="преобразование строк из HTTP-запроса в типы данных Python">Парсинг</abbr>" данных
|
||||
* Валидацию данных
|
||||
* Автоматическую документацию API с указанием типов параметров.
|
||||
* Аннотации API и автоматическую документацию
|
||||
|
||||
И объявлять типы достаточно один раз.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Модели Query-Параметров
|
||||
# Модели Query-Параметров { #query-parameter-models }
|
||||
|
||||
Если у вас есть группа связанных **query-параметров**, то вы можете объединить их в одну **Pydantic-модель**.
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
///
|
||||
|
||||
## Pydantic-Модель для Query-Параметров
|
||||
## Pydantic-Модель для Query-Параметров { #query-parameters-with-a-pydantic-model }
|
||||
|
||||
Объявите нужные **query-параметры** в **Pydantic-модели**, а после аннотируйте параметр как `Query`:
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
**FastAPI извлечёт** данные соответствующие **каждому полю модели** из **query-параметров** запроса и выдаст вам объявленную Pydantic-модель заполненную ими.
|
||||
|
||||
## Проверьте Сгенерированную Документацию
|
||||
## Проверьте Сгенерированную Документацию { #check-the-docs }
|
||||
|
||||
Вы можете посмотреть query-параметры в графическом интерфейсе сгенерированной документации по пути `/docs`:
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<img src="/img/tutorial/query-param-models/image01.png">
|
||||
</div>
|
||||
|
||||
## Запретить Дополнительные Query-Параметры
|
||||
## Запретить Дополнительные Query-Параметры { #forbid-extra-query-parameters }
|
||||
|
||||
В некоторых случаях (не особо часто встречающихся) вам может понадобиться **ограничить** query-параметры, которые вы хотите получить.
|
||||
|
||||
@@ -57,12 +57,12 @@ https://example.com/items/?limit=10&tool=plumbus
|
||||
}
|
||||
```
|
||||
|
||||
## Заключение
|
||||
## Заключение { #summary }
|
||||
|
||||
Вы можете использовать **Pydantic-модели** для объявления **query-параметров** в **FastAPI**. 😎
|
||||
|
||||
/// tip | Совет
|
||||
|
||||
Спойлер: вы также можете использовать Pydantic-модели для группировки кук (cookies) и заголовков (headers), но об этом вы прочитаете позже. 🤫
|
||||
Спойлер: вы также можете использовать Pydantic-модели, чтобы объявлять cookies и HTTP-заголовки, но об этом вы прочитаете позже. 🤫
|
||||
|
||||
///
|
||||
|
||||
@@ -1,61 +1,51 @@
|
||||
# Query-параметры и валидация строк
|
||||
# Query-параметры и валидация строк { #query-parameters-and-string-validations }
|
||||
|
||||
**FastAPI** позволяет определять дополнительную информацию и валидацию для ваших параметров.
|
||||
**FastAPI** позволяет определять дополнительную информацию и выполнять валидацию для ваших параметров.
|
||||
|
||||
Давайте рассмотрим следующий пример:
|
||||
Рассмотрим это приложение в качестве примера:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial001_py310.py hl[7] *}
|
||||
|
||||
Query-параметр `q` имеет тип `Union[str, None]` (или `str | None` в Python 3.10). Это означает, что входной параметр будет типа `str`, но может быть и `None`. Ещё параметр имеет значение по умолчанию `None`, из-за чего FastAPI определит параметр как необязательный.
|
||||
Query-параметр `q` имеет тип `str | None`, это означает, что он имеет тип `str`, но также может быть `None`. Значение по умолчанию действительно `None`, поэтому FastAPI будет знать, что он не обязателен.
|
||||
|
||||
/// note | Технические детали
|
||||
|
||||
FastAPI определит параметр `q` как необязательный, потому что его значение по умолчанию `= None`.
|
||||
FastAPI поймёт, что значение `q` не обязательно, из‑за значения по умолчанию `= None`.
|
||||
|
||||
`Union` в `Union[str, None]` позволит редактору кода оказать вам лучшую поддержку и найти ошибки.
|
||||
Аннотация `str | None` позволит вашему редактору кода обеспечить лучшую поддержку и находить ошибки.
|
||||
|
||||
///
|
||||
|
||||
## Расширенная валидация
|
||||
## Дополнительная валидация { #additional-validation }
|
||||
|
||||
Добавим дополнительное условие валидации параметра `q` - **длина строки не более 50 символов** (условие проверяется всякий раз, когда параметр `q` не является `None`).
|
||||
Мы собираемся добавить ограничение: хотя `q` и необязателен, когда он передан, **его длина не должна превышать 50 символов**.
|
||||
|
||||
### Импорт `Query` и `Annotated`
|
||||
### Импорт `Query` и `Annotated` { #import-query-and-annotated }
|
||||
|
||||
Чтобы достичь этого, первым делом нам нужно импортировать:
|
||||
Чтобы сделать это, сначала импортируйте:
|
||||
|
||||
* `Query` из пакета `fastapi`:
|
||||
* `Annotated` из пакета `typing` (или из `typing_extensions` для Python ниже 3.9)
|
||||
* `Query` из `fastapi`
|
||||
* `Annotated` из `typing`
|
||||
|
||||
//// tab | Python 3.10+
|
||||
{* ../../docs_src/query_params_str_validations/tutorial002_an_py310.py hl[1,3] *}
|
||||
|
||||
В Python 3.9 или выше, `Annotated` является частью стандартной библиотеки, таким образом вы можете импортировать его из `typing`.
|
||||
/// info | Дополнительная информация
|
||||
|
||||
```Python hl_lines="1 3"
|
||||
{!> ../../docs_src/query_params_str_validations/tutorial002_an_py310.py!}
|
||||
```
|
||||
Поддержка `Annotated` (и рекомендация использовать его) появилась в FastAPI версии 0.95.0.
|
||||
|
||||
////
|
||||
Если у вас более старая версия, при попытке использовать `Annotated` вы получите ошибки.
|
||||
|
||||
//// tab | Python 3.8+
|
||||
Убедитесь, что вы [обновили версию FastAPI](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} как минимум до 0.95.1 перед использованием `Annotated`.
|
||||
|
||||
В версиях Python ниже Python 3.9 `Annotation` импортируется из `typing_extensions`.
|
||||
///
|
||||
|
||||
Эта библиотека будет установлена вместе с FastAPI.
|
||||
## Использовать `Annotated` в типе для параметра `q` { #use-annotated-in-the-type-for-the-q-parameter }
|
||||
|
||||
```Python hl_lines="3-4"
|
||||
{!> ../../docs_src/query_params_str_validations/tutorial002_an.py!}
|
||||
```
|
||||
Помните, я уже говорил, что `Annotated` можно использовать для добавления метаданных к параметрам в разделе [Введение в типы Python](../python-types.md#type-hints-with-metadata-annotations){.internal-link target=_blank}?
|
||||
|
||||
////
|
||||
Пришло время использовать его с FastAPI. 🚀
|
||||
|
||||
## `Annotated` как тип для query-параметра `q`
|
||||
|
||||
Помните, как ранее я говорил об Annotated? Он может быть использован для добавления метаданных для ваших параметров в разделе [Введение в аннотации типов Python](../python-types.md#type-hints-with-metadata-annotations){.internal-link target=_blank}?
|
||||
|
||||
Пришло время использовать их в FastAPI. 🚀
|
||||
|
||||
У нас была аннотация следующего типа:
|
||||
У нас была такая аннотация типа:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
@@ -73,7 +63,7 @@ q: Union[str, None] = None
|
||||
|
||||
////
|
||||
|
||||
Вот что мы получим, если обернём это в `Annotated`:
|
||||
Мы «обернём» это в `Annotated`, и получится:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
@@ -91,173 +81,161 @@ q: Annotated[Union[str, None]] = None
|
||||
|
||||
////
|
||||
|
||||
Обе эти версии означают одно и тоже. `q` - это параметр, который может быть `str` или `None`, и по умолчанию он будет принимать `None`.
|
||||
Обе версии означают одно и то же: `q` — параметр, который может быть `str` или `None`, и по умолчанию равен `None`.
|
||||
|
||||
Давайте повеселимся. 🎉
|
||||
А теперь к самому интересному. 🎉
|
||||
|
||||
## Добавим `Query` в `Annotated` для query-параметра `q`
|
||||
## Добавим `Query` в `Annotated` для параметра `q` { #add-query-to-annotated-in-the-q-parameter }
|
||||
|
||||
Теперь, когда у нас есть `Annotated`, где мы можем добавить больше метаданных, добавим `Query` со значением параметра `max_length` равным 50:
|
||||
Теперь, когда у нас есть `Annotated`, куда можно поместить дополнительную информацию (в нашем случае — дополнительные правила валидации), добавим `Query` внутрь `Annotated` и установим параметр `max_length` равным `50`:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial002_an_py310.py hl[9] *}
|
||||
|
||||
Обратите внимание, что значение по умолчанию всё ещё `None`, так что параметр остаётся необязательным.
|
||||
Обратите внимание, что значение по умолчанию по‑прежнему `None`, то есть параметр остаётся необязательным.
|
||||
|
||||
Однако теперь, имея `Query(max_length=50)` внутри `Annotated`, мы говорим FastAPI, что мы хотим извлечь это значение из параметров query-запроса (что произойдёт в любом случае 🤷), и что мы хотим иметь **дополнительные условия валидации** для этого значения (для чего мы и делаем это - чтобы получить дополнительную валидацию). 😎
|
||||
Но теперь, добавив `Query(max_length=50)` внутрь `Annotated`, мы говорим FastAPI, что этому значению нужна **дополнительная валидация** — максимум 50 символов. 😎
|
||||
|
||||
Теперь FastAPI:
|
||||
/// tip | Совет
|
||||
|
||||
* **Валидирует** (проверяет), что полученные данные состоят максимум из 50 символов
|
||||
* Показывает **исчерпывающую ошибку** (будет описание местонахождения ошибки и её причины) для клиента в случаях, когда данные не валидны
|
||||
* **Задокументирует** параметр в схему OpenAPI *операции пути* (что будет отображено в **UI автоматической документации**)
|
||||
|
||||
## Альтернативный (устаревший) способ задать `Query` как значение по умолчанию
|
||||
|
||||
В предыдущих версиях FastAPI (ниже <abbr title="ранее 2023-03">0.95.0</abbr>) необходимо было использовать `Query` как значение по умолчанию для query-параметра. Так было вместо размещения его в `Annotated`, так что велика вероятность, что вам встретится такой код. Сейчас объясню.
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
При написании нового кода и везде где это возможно, используйте `Annotated`, как было описано ранее. У этого способа есть несколько преимуществ (о них дальше) и никаких недостатков. 🍰
|
||||
Здесь мы используем `Query()`, потому что это **query-параметр**. Позже мы увидим другие — `Path()`, `Body()`, `Header()` и `Cookie()`, — они также принимают те же аргументы, что и `Query()`.
|
||||
|
||||
///
|
||||
|
||||
Вот как вы могли бы использовать `Query()` в качестве значения по умолчанию параметра вашей функции, установив для параметра `max_length` значение 50:
|
||||
Теперь FastAPI будет:
|
||||
|
||||
* **валидировать** данные, удостоверяясь, что максимальная длина — 50 символов;
|
||||
* показывать **понятную ошибку** клиенту, если данные невалидны;
|
||||
* **документировать** параметр в *операции пути* схемы OpenAPI (он будет показан в **UI автоматической документации**).
|
||||
|
||||
## Альтернатива (устаревшее): `Query` как значение по умолчанию { #alternative-old-query-as-the-default-value }
|
||||
|
||||
В предыдущих версиях FastAPI (до <abbr title="до 2023-03">0.95.0</abbr>) требовалось использовать `Query` как значение по умолчанию для параметра вместо помещения его в `Annotated`. Скорее всего вы ещё встретите такой код, поэтому поясню.
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Для нового кода и везде, где это возможно, используйте `Annotated`, как описано выше. У этого есть несколько преимуществ (см. ниже) и нет недостатков. 🍰
|
||||
|
||||
///
|
||||
|
||||
Вот как можно использовать `Query()` как значение по умолчанию для параметра функции, установив `max_length` равным 50:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial002_py310.py hl[7] *}
|
||||
|
||||
В таком случае (без использования `Annotated`), мы заменили значение по умолчанию с `None` на `Query()` в функции. Теперь нам нужно установить значение по умолчанию для query-параметра `Query(default=None)`, что необходимо для тех же целей, как когда ранее просто указывалось значение по умолчанию (по крайней мере, для FastAPI).
|
||||
Так как в этом случае (без `Annotated`) мы заменяем в функции значение по умолчанию `None` на `Query()`, теперь нужно указать значение по умолчанию через параметр `Query(default=None)`, это служит той же цели — задать значение по умолчанию (по крайней мере для FastAPI).
|
||||
|
||||
Таким образом:
|
||||
|
||||
```Python
|
||||
q: Union[str, None] = Query(default=None)
|
||||
```
|
||||
|
||||
...делает параметр необязательным со значением по умолчанию `None`, также как это делает:
|
||||
|
||||
```Python
|
||||
q: Union[str, None] = None
|
||||
```
|
||||
|
||||
И для Python 3.10 и выше:
|
||||
Итак:
|
||||
|
||||
```Python
|
||||
q: str | None = Query(default=None)
|
||||
```
|
||||
|
||||
...делает параметр необязательным со значением по умолчанию `None`, также как это делает:
|
||||
...делает параметр необязательным со значением по умолчанию `None`, так же как:
|
||||
|
||||
```Python
|
||||
q: str | None = None
|
||||
```
|
||||
|
||||
Но он явно объявляет его как query-параметр.
|
||||
Но вариант с `Query` явно объявляет его как query-параметр.
|
||||
|
||||
/// info | Дополнительная информация
|
||||
|
||||
Запомните, важной частью объявления параметра как необязательного является:
|
||||
Затем мы можем передать и другие параметры в `Query`. В данном случае — параметр `max_length`, применимый к строкам:
|
||||
|
||||
```Python
|
||||
= None
|
||||
q: str | None = Query(default=None, max_length=50)
|
||||
```
|
||||
|
||||
или:
|
||||
Это провалидирует данные, покажет понятную ошибку, если данные невалидны, и задокументирует параметр в *операции пути* схемы OpenAPI.
|
||||
|
||||
```Python
|
||||
= Query(default=None)
|
||||
```
|
||||
### `Query` как значение по умолчанию или внутри `Annotated` { #query-as-the-default-value-or-in-annotated }
|
||||
|
||||
так как `None` указан в качестве значения по умолчанию, параметр будет **необязательным**.
|
||||
Помните, что при использовании `Query` внутри `Annotated` нельзя указывать параметр `default` у `Query`.
|
||||
|
||||
`Union[str, None]` позволит редактору кода оказать вам лучшую поддержку. Но это не то, на что обращает внимание FastAPI для определения необязательности параметра.
|
||||
Вместо этого используйте обычное значение по умолчанию параметра функции. Иначе это будет неоднозначно.
|
||||
|
||||
///
|
||||
|
||||
Теперь, мы можем указать больше параметров для `Query`. В данном случае, параметр `max_length` применяется к строкам:
|
||||
|
||||
```Python
|
||||
q: Union[str, None] = Query(default=None, max_length=50)
|
||||
```
|
||||
|
||||
Входные данные будут проверены. Если данные недействительны, тогда будет указано на ошибку в запросе (будет описание местонахождения ошибки и её причины). Кроме того, параметр задокументируется в схеме OpenAPI данной *операции пути*.
|
||||
|
||||
### Использовать `Query` как значение по умолчанию или добавить в `Annotated`
|
||||
|
||||
Когда `Query` используется внутри `Annotated`, вы не можете использовать параметр `default` у `Query`.
|
||||
|
||||
Вместо этого, используйте обычное указание значения по умолчанию для параметра функции. Иначе, это будет несовместимо.
|
||||
|
||||
Следующий пример не рабочий:
|
||||
Например, так делать нельзя:
|
||||
|
||||
```Python
|
||||
q: Annotated[str, Query(default="rick")] = "morty"
|
||||
```
|
||||
|
||||
...потому что нельзя однозначно определить, что именно должно быть значением по умолчанию: `"rick"` или `"morty"`.
|
||||
...потому что непонятно, какое значение должно быть по умолчанию: `"rick"` или `"morty"`.
|
||||
|
||||
Вам следует использовать (предпочтительно):
|
||||
Следовательно, используйте (предпочтительно):
|
||||
|
||||
```Python
|
||||
q: Annotated[str, Query()] = "rick"
|
||||
```
|
||||
|
||||
...или как в старом коде, который вам может попасться:
|
||||
...или в старой кодовой базе вы увидите:
|
||||
|
||||
```Python
|
||||
q: str = Query(default="rick")
|
||||
```
|
||||
|
||||
### Преимущества `Annotated`
|
||||
### Преимущества `Annotated` { #advantages-of-annotated }
|
||||
|
||||
**Рекомендуется использовать `Annotated`** вместо значения по умолчанию в параметрах функции, потому что так **лучше** по нескольким причинам. 🤓
|
||||
**Рекомендуется использовать `Annotated`** вместо задания значения по умолчанию в параметрах функции — так **лучше** по нескольким причинам. 🤓
|
||||
|
||||
Значение **по умолчанию** у **параметров функции** - это **действительно значение по умолчанию**, что более интуитивно понятно для пользователей Python. 😌
|
||||
**Значение по умолчанию** у **параметра функции** — это **настоящее значение по умолчанию**, что более интуитивно для Python. 😌
|
||||
|
||||
Вы можете **вызвать** ту же функцию в **иных местах** без FastAPI, и она **сработает как ожидается**. Если это **обязательный** параметр (без значения по умолчанию), ваш **редактор кода** сообщит об ошибке. **Python** также укажет на ошибку, если вы вызовете функцию без передачи ей обязательного параметра.
|
||||
Вы можете **вызвать** эту же функцию в **других местах** без FastAPI, и она будет **работать как ожидается**. Если есть **обязательный** параметр (без значения по умолчанию), ваш **редактор кода** сообщит об ошибке, **Python** тоже пожалуется, если вы запустите её без передачи обязательного параметра.
|
||||
|
||||
Если вы вместо `Annotated` используете **(устаревший) стиль значений по умолчанию**, тогда при вызове этой функции без FastAPI в **другом месте** вам необходимо **помнить** о передаче аргументов функции, чтобы она работала корректно. В противном случае, значения будут отличаться от тех, что вы ожидаете (например, `QueryInfo` или что-то подобное вместо `str`). И ни ваш редактор кода, ни Python не будут жаловаться на работу этой функции, только когда вычисления внутри дадут сбой.
|
||||
Если вы не используете `Annotated`, а применяете **(устаревший) стиль со значением по умолчанию**, то при вызове этой функции без FastAPI в **других местах** вам нужно **помнить** о том, что надо передать аргументы, чтобы всё работало корректно, иначе значения будут не такими, как вы ожидаете (например, вместо `str` будет `QueryInfo` или что-то подобное). И ни редактор, ни Python не будут ругаться при самом вызове функции — ошибка проявится лишь при операциях внутри.
|
||||
|
||||
Так как `Annotated` может принимать более одной аннотации метаданных, то теперь вы можете использовать ту же функцию с другими инструментами, например <a href="https://typer.tiangolo.com/" class="external-link" target="_blank">Typer</a>. 🚀
|
||||
Так как `Annotated` может содержать больше одной аннотации метаданных, теперь вы можете использовать ту же функцию и с другими инструментами, например с <a href="https://typer.tiangolo.com/" class="external-link" target="_blank">Typer</a>. 🚀
|
||||
|
||||
## Больше валидации
|
||||
## Больше валидаций { #add-more-validations }
|
||||
|
||||
Вы также можете добавить параметр `min_length`:
|
||||
Можно также добавить параметр `min_length`:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial003_an_py310.py hl[10] *}
|
||||
|
||||
## Регулярные выражения
|
||||
## Регулярные выражения { #add-regular-expressions }
|
||||
|
||||
Вы можете определить <abbr title="Регулярное выражение, regex или regexp - это последовательность символов, определяющая шаблон для строк.">регулярное выражение</abbr>, которому должен соответствовать параметр:
|
||||
Вы можете определить <abbr title="Регулярное выражение (regex, regexp) — это последовательность символов, задающая шаблон поиска для строк.">регулярное выражение</abbr> `pattern`, которому должен соответствовать параметр:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial004_an_py310.py hl[11] *}
|
||||
|
||||
Данное регулярное выражение проверяет, что полученное значение параметра:
|
||||
Данный шаблон регулярного выражения проверяет, что полученное значение параметра:
|
||||
|
||||
* `^`: начало строки.
|
||||
* `fixedquery`: в точности содержит строку `fixedquery`.
|
||||
* `$`: конец строки, не имеет символов после `fixedquery`.
|
||||
* `^`: начинается с следующих символов, до них нет символов.
|
||||
* `fixedquery`: имеет точное значение `fixedquery`.
|
||||
* `$`: заканчивается здесь, после `fixedquery` нет никаких символов.
|
||||
|
||||
Не переживайте, если **"регулярное выражение"** вызывает у вас трудности. Это достаточно сложная тема для многих людей. Вы можете сделать множество вещей без использования регулярных выражений.
|
||||
Если вы теряетесь во всех этих идеях про **«регулярные выражения»**, не переживайте. Это сложная тема для многих. Многое можно сделать и без них.
|
||||
|
||||
Но когда они вам понадобятся, и вы закончите их освоение, то не будет проблемой использовать их в **FastAPI**.
|
||||
Теперь вы знаете, что когда они понадобятся, вы сможете использовать их в **FastAPI**.
|
||||
|
||||
## Значения по умолчанию
|
||||
### `regex` из Pydantic v1 вместо `pattern` { #pydantic-v1-regex-instead-of-pattern }
|
||||
|
||||
Вы точно также можете указать любое значение `по умолчанию`, как ранее указывали `None`.
|
||||
До Pydantic версии 2 и до FastAPI 0.100.0 этот параметр назывался `regex`, а не `pattern`, но сейчас он устарел.
|
||||
|
||||
Например, вы хотите для параметра запроса `q` указать, что он должен состоять минимум из 3 символов (`min_length=3`) и иметь значение по умолчанию `"fixedquery"`:
|
||||
Вы всё ещё можете встретить такой код:
|
||||
|
||||
//// tab | Pydantic v1
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial004_regex_an_py310.py hl[11] *}
|
||||
|
||||
////
|
||||
|
||||
Имейте в виду, что это устарело, и код следует обновить на использование нового параметра `pattern`. 🤓
|
||||
|
||||
## Значения по умолчанию { #default-values }
|
||||
|
||||
Конечно, можно использовать и другие значения по умолчанию, не только `None`.
|
||||
|
||||
Допустим, вы хотите объявить, что query-параметр `q` должен иметь `min_length` равный `3` и значение по умолчанию `"fixedquery"`:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial005_an_py39.py hl[9] *}
|
||||
|
||||
/// note | Технические детали
|
||||
/// note | Примечание
|
||||
|
||||
Наличие значения по умолчанию делает параметр необязательным.
|
||||
Наличие значения по умолчанию любого типа, включая `None`, делает параметр необязательным.
|
||||
|
||||
///
|
||||
|
||||
## Обязательный параметр
|
||||
## Обязательные параметры { #required-parameters }
|
||||
|
||||
Когда вам не требуется дополнительная валидация или дополнительные метаданные для параметра запроса, вы можете сделать параметр `q` обязательным просто не указывая значения по умолчанию. Например:
|
||||
Когда не требуется объявлять дополнительные проверки или метаданные, можно сделать query-параметр `q` обязательным, просто не указывая значение по умолчанию, например:
|
||||
|
||||
```Python
|
||||
q: str
|
||||
@@ -266,60 +244,42 @@ q: str
|
||||
вместо:
|
||||
|
||||
```Python
|
||||
q: Union[str, None] = None
|
||||
q: str | None = None
|
||||
```
|
||||
|
||||
Но у нас query-параметр определён как `Query`. Например:
|
||||
|
||||
//// tab | Annotated
|
||||
Но сейчас мы объявляем его через `Query`, например так:
|
||||
|
||||
```Python
|
||||
q: Annotated[Union[str, None], Query(min_length=3)] = None
|
||||
q: Annotated[str | None, Query(min_length=3)] = None
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | без Annotated
|
||||
|
||||
```Python
|
||||
q: Union[str, None] = Query(default=None, min_length=3)
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
В таком случае, чтобы сделать query-параметр `Query` обязательным, вы можете просто не указывать значение по умолчанию:
|
||||
Поэтому, когда вам нужно объявить значение как обязательное при использовании `Query`, просто не указывайте значение по умолчанию:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial006_an_py39.py hl[9] *}
|
||||
|
||||
### Обязательный параметр с `None`
|
||||
### Обязательный, но может быть `None` { #required-can-be-none }
|
||||
|
||||
Вы можете определить, что параметр может принимать `None`, но всё ещё является обязательным. Это может потребоваться для того, чтобы пользователи явно указали параметр, даже если его значение будет `None`.
|
||||
Можно объявить, что параметр может принимать `None`, но при этом остаётся обязательным. Это заставит клиентов отправлять значение, даже если это значение — `None`.
|
||||
|
||||
Чтобы этого добиться, вам нужно определить `None` как валидный тип для параметра запроса, но также указать `default=...`:
|
||||
Для этого объявите, что `None` — валидный тип, но просто не задавайте значение по умолчанию:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial006c_an_py310.py hl[9] *}
|
||||
|
||||
/// tip | Подсказка
|
||||
## Query-параметр - список / несколько значений { #query-parameter-list-multiple-values }
|
||||
|
||||
Pydantic, мощь которого используется в FastAPI для валидации и сериализации, имеет специальное поведение для `Optional` или `Union[Something, None]` без значения по умолчанию. Вы можете узнать об этом больше в документации Pydantic, раздел <a href="https://docs.pydantic.dev/latest/concepts/models/#required-optional-fields" class="external-link" target="_blank">Обязательные Опциональные поля</a>.
|
||||
Когда вы явно объявляете query-параметр через `Query`, можно также указать, что он принимает список значений, иначе говоря — несколько значений.
|
||||
|
||||
///
|
||||
|
||||
## Множество значений для query-параметра
|
||||
|
||||
Для query-параметра `Query` можно указать, что он принимает список значений (множество значений).
|
||||
|
||||
Например, query-параметр `q` может быть указан в URL несколько раз. И если вы ожидаете такой формат запроса, то можете указать это следующим образом:
|
||||
Например, чтобы объявить query-параметр `q`, который может встречаться в URL несколько раз, можно написать:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial011_an_py310.py hl[9] *}
|
||||
|
||||
Затем, получив такой URL:
|
||||
Тогда при таком URL:
|
||||
|
||||
```
|
||||
http://localhost:8000/items/?q=foo&q=bar
|
||||
```
|
||||
|
||||
вы бы получили несколько значений (`foo` и `bar`), которые относятся к параметру `q`, в виде Python `list` внутри вашей *функции обработки пути*, в *параметре функции* `q`.
|
||||
вы получите множественные значения query-параметра `q` (`foo` и `bar`) в виде Python-`list` внутри вашей *функции обработки пути*, в *параметре функции* `q`.
|
||||
|
||||
Таким образом, ответ на этот URL будет:
|
||||
|
||||
@@ -332,29 +292,29 @@ http://localhost:8000/items/?q=foo&q=bar
|
||||
}
|
||||
```
|
||||
|
||||
/// tip | Подсказка
|
||||
/// tip | Совет
|
||||
|
||||
Чтобы объявить query-параметр типом `list`, как в примере выше, вам нужно явно использовать `Query`, иначе он будет интерпретирован как тело запроса.
|
||||
Чтобы объявить query-параметр типа `list`, как в примере выше, нужно явно использовать `Query`, иначе он будет интерпретирован как тело запроса.
|
||||
|
||||
///
|
||||
|
||||
Интерактивная документация API будет обновлена соответствующим образом, где будет разрешено множество значений:
|
||||
Интерактивная документация API обновится соответствующим образом и позволит передавать несколько значений:
|
||||
|
||||
<img src="/img/tutorial/query-params-str-validations/image02.png">
|
||||
|
||||
### Query-параметр со множеством значений по умолчанию
|
||||
### Query-параметр - список / несколько значений со значением по умолчанию { #query-parameter-list-multiple-values-with-defaults }
|
||||
|
||||
Вы также можете указать тип `list` со списком значений по умолчанию на случай, если вам их не предоставят:
|
||||
Можно также определить значение по умолчанию как `list`, если ничего не передано:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial012_an_py39.py hl[9] *}
|
||||
|
||||
Если вы перейдёте по ссылке:
|
||||
Если вы перейдёте по адресу:
|
||||
|
||||
```
|
||||
http://localhost:8000/items/
|
||||
```
|
||||
|
||||
значение по умолчанию для `q` будет: `["foo", "bar"]` и ответом для вас будет:
|
||||
значение по умолчанию для `q` будет: `["foo", "bar"]`, и ответом будет:
|
||||
|
||||
```JSON
|
||||
{
|
||||
@@ -365,45 +325,45 @@ http://localhost:8000/items/
|
||||
}
|
||||
```
|
||||
|
||||
#### Использование `list`
|
||||
#### Просто `list` { #using-just-list }
|
||||
|
||||
Вы также можете использовать `list` напрямую вместо `List[str]` (или `list[str]` в Python 3.9+):
|
||||
Можно использовать `list` напрямую вместо `list[str]`:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial013_an_py39.py hl[9] *}
|
||||
|
||||
/// note | Технические детали
|
||||
|
||||
Запомните, что в таком случае, FastAPI не будет проверять содержимое списка.
|
||||
Имейте в виду, что в этом случае FastAPI не будет проверять содержимое списка.
|
||||
|
||||
Например, для List[int] список будет провалидирован (и задокументирован) на содержание только целочисленных элементов. Но для простого `list` такой проверки не будет.
|
||||
Например, `list[int]` проверит (и задокументирует), что элементы списка — целые числа. А просто `list` — нет.
|
||||
|
||||
///
|
||||
|
||||
## Больше метаданных
|
||||
## Больше метаданных { #declare-more-metadata }
|
||||
|
||||
Вы можете добавить больше информации об query-параметре.
|
||||
Можно добавить больше информации о параметре.
|
||||
|
||||
Указанная информация будет включена в генерируемую OpenAPI документацию и использована в пользовательском интерфейсе и внешних инструментах.
|
||||
Эта информация будет включена в сгенерированную OpenAPI-схему и использована интерфейсами документации и внешними инструментами.
|
||||
|
||||
/// note | Технические детали
|
||||
|
||||
Имейте в виду, что разные инструменты могут иметь разные уровни поддержки OpenAPI.
|
||||
Помните, что разные инструменты могут иметь разный уровень поддержки OpenAPI.
|
||||
|
||||
Некоторые из них могут не отображать (на данный момент) всю заявленную дополнительную информацию, хотя в большинстве случаев отсутствующая функция уже запланирована к разработке.
|
||||
Некоторые из них пока могут не показывать всю дополнительную информацию, хотя в большинстве случаев недостающая возможность уже запланирована к разработке.
|
||||
|
||||
///
|
||||
|
||||
Вы можете указать название query-параметра, используя параметр `title`:
|
||||
Можно задать `title`:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial007_an_py310.py hl[10] *}
|
||||
|
||||
Добавить описание, используя параметр `description`:
|
||||
И `description`:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial008_an_py310.py hl[14] *}
|
||||
|
||||
## Псевдонимы параметров
|
||||
## Псевдонимы параметров { #alias-parameters }
|
||||
|
||||
Представьте, что вы хотите использовать query-параметр с названием `item-query`.
|
||||
Представьте, что вы хотите, чтобы параметр назывался `item-query`.
|
||||
|
||||
Например:
|
||||
|
||||
@@ -411,54 +371,117 @@ http://localhost:8000/items/
|
||||
http://127.0.0.1:8000/items/?item-query=foobaritems
|
||||
```
|
||||
|
||||
Но `item-query` является невалидным именем переменной в Python.
|
||||
Но `item-query` — недопустимое имя переменной в Python.
|
||||
|
||||
Наиболее похожее валидное имя `item_query`.
|
||||
Ближайший вариант — `item_query`.
|
||||
|
||||
Но вам всё равно необходим `item-query`...
|
||||
Но вам всё равно нужно именно `item-query`...
|
||||
|
||||
Тогда вы можете объявить `псевдоним`, и этот псевдоним будет использоваться для поиска значения параметра запроса:
|
||||
Тогда можно объявить `alias`, и этот псевдоним будет использован для поиска значения параметра:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial009_an_py310.py hl[9] *}
|
||||
|
||||
## Устаревшие параметры
|
||||
## Маркировка параметров как устаревших { #deprecating-parameters }
|
||||
|
||||
Предположим, вы больше не хотите использовать какой-либо параметр.
|
||||
Предположим, этот параметр вам больше не нравится.
|
||||
|
||||
Вы решили оставить его, потому что клиенты всё ещё им пользуются. Но вы хотите отобразить это в документации как <abbr title="устарело, не рекомендуется использовать">устаревший функционал</abbr>.
|
||||
Его нужно оставить на какое‑то время, так как клиенты его используют, но вы хотите, чтобы в документации он явно отображался как <abbr title="устаревший, не рекомендуется использовать">устаревший</abbr>.
|
||||
|
||||
Тогда для `Query` укажите параметр `deprecated=True`:
|
||||
Тогда передайте параметр `deprecated=True` в `Query`:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial010_an_py310.py hl[19] *}
|
||||
|
||||
В документации это будет отображено следующим образом:
|
||||
В документации это будет показано так:
|
||||
|
||||
<img src="/img/tutorial/query-params-str-validations/image01.png">
|
||||
|
||||
## Исключить из OpenAPI
|
||||
## Исключить параметры из OpenAPI { #exclude-parameters-from-openapi }
|
||||
|
||||
Чтобы исключить query-параметр из генерируемой OpenAPI схемы (а также из системы автоматической генерации документации), укажите в `Query` параметр `include_in_schema=False`:
|
||||
Чтобы исключить query-параметр из генерируемой OpenAPI-схемы (и, следовательно, из систем автоматической документации), укажите у `Query` параметр `include_in_schema=False`:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial014_an_py310.py hl[10] *}
|
||||
|
||||
## Резюме
|
||||
## Кастомная валидация { #custom-validation }
|
||||
|
||||
Вы можете объявлять дополнительные правила валидации и метаданные для ваших параметров запроса.
|
||||
Бывают случаи, когда нужна **кастомная валидация**, которую нельзя выразить параметрами выше.
|
||||
|
||||
Общие метаданные:
|
||||
В таких случаях можно использовать **кастомную функцию-валидатор**, которая применяется после обычной валидации (например, после проверки, что значение — это `str`).
|
||||
|
||||
Этого можно добиться, используя <a href="https://docs.pydantic.dev/latest/concepts/validators/#field-after-validator" class="external-link" target="_blank">`AfterValidator` Pydantic</a> внутри `Annotated`.
|
||||
|
||||
/// tip | Совет
|
||||
|
||||
В Pydantic также есть <a href="https://docs.pydantic.dev/latest/concepts/validators/#field-before-validator" class="external-link" target="_blank">`BeforeValidator`</a> и другие. 🤓
|
||||
|
||||
///
|
||||
|
||||
Например, эта кастомная проверка убеждается, что ID элемента начинается с `isbn-` для номера книги <abbr title="ISBN означает International Standard Book Number – Международный стандартный книжный номер">ISBN</abbr> или с `imdb-` для ID URL фильма на <abbr title="IMDB (Internet Movie Database) — веб‑сайт с информацией о фильмах">IMDB</abbr>:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py hl[5,16:19,24] *}
|
||||
|
||||
/// info | Дополнительная информация
|
||||
|
||||
Это доступно в Pydantic версии 2 и выше. 😎
|
||||
|
||||
///
|
||||
|
||||
/// tip | Совет
|
||||
|
||||
Если вам нужна валидация, требующая общения с каким‑либо **внешним компонентом** — базой данных или другим API — вместо этого используйте **Зависимости FastAPI** (FastAPI Dependencies), вы познакомитесь с ними позже.
|
||||
|
||||
Эти кастомные валидаторы предназначены для проверок, которые можно выполнить, имея **только** те же **данные**, что пришли в запросе.
|
||||
|
||||
///
|
||||
|
||||
### Понимание этого кода { #understand-that-code }
|
||||
|
||||
Важный момент — это использовать **`AfterValidator` с функцией внутри `Annotated`**. Смело пропускайте эту часть. 🤸
|
||||
|
||||
---
|
||||
|
||||
Но если вам любопытен именно этот пример и всё ещё интересно, вот немного подробностей.
|
||||
|
||||
#### Строка и `value.startswith()` { #string-with-value-startswith }
|
||||
|
||||
Заметили? Метод строки `value.startswith()` может принимать кортеж — тогда будет проверено каждое значение из кортежа:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py ln[16:19] hl[17] *}
|
||||
|
||||
#### Случайный элемент { #a-random-item }
|
||||
|
||||
С помощью `data.items()` мы получаем <abbr title="Объект, по которому можно итерироваться циклом for, например список, множество и т. п.">итерируемый объект</abbr> с кортежами, содержащими ключ и значение для каждого элемента словаря.
|
||||
|
||||
Мы превращаем этот итерируемый объект в обычный `list` через `list(data.items())`.
|
||||
|
||||
Затем с `random.choice()` можно получить **случайное значение** из списка — то есть кортеж вида `(id, name)`. Это будет что‑то вроде `("imdb-tt0371724", "The Hitchhiker's Guide to the Galaxy")`.
|
||||
|
||||
После этого мы **распаковываем** эти два значения кортежа в переменные `id` и `name`.
|
||||
|
||||
Так что, если пользователь не передал ID элемента, он всё равно получит случайную рекомендацию.
|
||||
|
||||
...и всё это в **одной простой строке**. 🤯 Разве не прекрасен Python? 🐍
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py ln[22:30] hl[29] *}
|
||||
|
||||
## Резюме { #recap }
|
||||
|
||||
Вы можете объявлять дополнительные проверки и метаданные для параметров.
|
||||
|
||||
Общие метаданные и настройки:
|
||||
|
||||
* `alias`
|
||||
* `title`
|
||||
* `description`
|
||||
* `deprecated`
|
||||
* `include_in_schema`
|
||||
|
||||
Специфичные правила валидации для строк:
|
||||
Проверки, специфичные для строк:
|
||||
|
||||
* `min_length`
|
||||
* `max_length`
|
||||
* `regex`
|
||||
* `pattern`
|
||||
|
||||
В рассмотренных примерах показано объявление правил валидации для строковых значений `str`.
|
||||
Кастомные проверки с использованием `AfterValidator`.
|
||||
|
||||
В следующих главах вы увидете, как объявлять правила валидации для других типов (например, чисел).
|
||||
В этих примерах вы видели, как объявлять проверки для значений типа `str`.
|
||||
|
||||
Смотрите следующие главы, чтобы узнать, как объявлять проверки для других типов, например чисел.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Query-параметры
|
||||
# Query-параметры { #query-parameters }
|
||||
|
||||
Когда вы объявляете параметры функции, которые не являются параметрами пути, они автоматически интерпретируются как "query"-параметры.
|
||||
Когда вы объявляете параметры функции, которые не являются параметрами пути, они автоматически интерпретируются как "query"-параметры.
|
||||
|
||||
{* ../../docs_src/query_params/tutorial001.py hl[9] *}
|
||||
|
||||
@@ -19,7 +19,7 @@ http://127.0.0.1:8000/items/?skip=0&limit=10
|
||||
|
||||
Будучи частью URL-адреса, они "по умолчанию" являются строками.
|
||||
|
||||
Но когда вы объявляете их с использованием аннотаций (в примере выше, как `int`), они конвертируются в указанный тип данных и проходят проверку на соответствие ему.
|
||||
Но когда вы объявляете их с использованием типов Python (в примере выше, как `int`), они конвертируются в указанный тип данных и проходят проверку на соответствие ему.
|
||||
|
||||
Все те же правила, которые применяются к path-параметрам, также применяются и query-параметрам:
|
||||
|
||||
@@ -28,7 +28,7 @@ http://127.0.0.1:8000/items/?skip=0&limit=10
|
||||
* Проверка на соответствие данных (Валидация)
|
||||
* Автоматическая документация
|
||||
|
||||
## Значения по умолчанию
|
||||
## Значения по умолчанию { #defaults }
|
||||
|
||||
Поскольку query-параметры не являются фиксированной частью пути, они могут быть не обязательными и иметь значения по умолчанию.
|
||||
|
||||
@@ -57,7 +57,7 @@ http://127.0.0.1:8000/items/?skip=20
|
||||
* `skip=20`: потому что вы установили это в URL-адресе
|
||||
* `limit=10`: т.к это было значение по умолчанию
|
||||
|
||||
## Необязательные параметры
|
||||
## Необязательные параметры { #optional-parameters }
|
||||
|
||||
Аналогично, вы можете объявлять необязательные query-параметры, установив их значение по умолчанию, равное `None`:
|
||||
|
||||
@@ -71,7 +71,7 @@ http://127.0.0.1:8000/items/?skip=20
|
||||
|
||||
///
|
||||
|
||||
## Преобразование типа параметра запроса
|
||||
## Преобразование типа параметра запроса { #query-parameter-type-conversion }
|
||||
|
||||
Вы также можете объявлять параметры с типом `bool`, которые будут преобразованы соответственно:
|
||||
|
||||
@@ -109,10 +109,9 @@ http://127.0.0.1:8000/items/foo?short=yes
|
||||
|
||||
или в любом другом варианте написания (в верхнем регистре, с заглавной буквой, и т.п), внутри вашей функции параметр `short` будет иметь значение `True` типа данных `bool` . В противном случае - `False`.
|
||||
|
||||
## Смешивание query-параметров и path-параметров { #multiple-path-and-query-parameters }
|
||||
|
||||
## Смешивание query-параметров и path-параметров
|
||||
|
||||
Вы можете объявлять несколько query-параметров и path-параметров одновременно,**FastAPI** сам разберётся, что чем является.
|
||||
Вы можете объявлять несколько query-параметров и path-параметров одновременно, **FastAPI** сам разберётся, что чем является.
|
||||
|
||||
И вы не обязаны объявлять их в каком-либо определенном порядке.
|
||||
|
||||
@@ -120,9 +119,9 @@ http://127.0.0.1:8000/items/foo?short=yes
|
||||
|
||||
{* ../../docs_src/query_params/tutorial004_py310.py hl[6,8] *}
|
||||
|
||||
## Обязательные query-параметры
|
||||
## Обязательные query-параметры { #required-query-parameters }
|
||||
|
||||
Когда вы объявляете значение по умолчанию для параметра, который не является path-параметром (в этом разделе, мы пока что познакомились только с path-параметрами), то это значение не является обязательным.
|
||||
Когда вы объявляете значение по умолчанию для параметра, который не является path-параметром (в этом разделе, мы пока что познакомились только с path-параметрами), то он не является обязательным.
|
||||
|
||||
Если вы не хотите задавать конкретное значение, но хотите сделать параметр необязательным, вы можете установить значение по умолчанию равным `None`.
|
||||
|
||||
@@ -142,16 +141,17 @@ http://127.0.0.1:8000/items/foo-item
|
||||
|
||||
```JSON
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": [
|
||||
"query",
|
||||
"needy"
|
||||
],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing"
|
||||
}
|
||||
]
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": [
|
||||
"query",
|
||||
"needy"
|
||||
],
|
||||
"msg": "Field required",
|
||||
"input": null
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -170,7 +170,7 @@ http://127.0.0.1:8000/items/foo-item?needy=sooooneedy
|
||||
}
|
||||
```
|
||||
|
||||
Конечно, вы можете определить некоторые параметры как обязательные, некоторые - со значением по умполчанию, а некоторые - полностью необязательные:
|
||||
Конечно, вы можете определить некоторые параметры как обязательные, некоторые — со значением по умолчанию, а некоторые — полностью необязательные:
|
||||
|
||||
{* ../../docs_src/query_params/tutorial006_py310.py hl[8] *}
|
||||
|
||||
@@ -182,6 +182,6 @@ http://127.0.0.1:8000/items/foo-item?needy=sooooneedy
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Вы можете использовать класс `Enum` также, как ранее применяли его с [Path-параметрами](path-params.md#_7){.internal-link target=_blank}.
|
||||
Вы можете использовать класс `Enum` также, как ранее применяли его с [Path-параметрами](path-params.md#predefined-values){.internal-link target=_blank}.
|
||||
|
||||
///
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Загрузка файлов
|
||||
# Загрузка файлов { #request-files }
|
||||
|
||||
Используя класс `File`, мы можем позволить клиентам загружать файлы.
|
||||
|
||||
@@ -6,19 +6,23 @@
|
||||
|
||||
Чтобы получать загруженные файлы, сначала установите <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>.
|
||||
|
||||
Например: `pip install python-multipart`.
|
||||
Убедитесь, что вы создали [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активировали его, а затем установили пакет, например:
|
||||
|
||||
Это связано с тем, что загружаемые файлы передаются как данные формы.
|
||||
```console
|
||||
$ pip install python-multipart
|
||||
```
|
||||
|
||||
Это связано с тем, что загружаемые файлы передаются как "данные формы".
|
||||
|
||||
///
|
||||
|
||||
## Импорт `File`
|
||||
## Импорт `File` { #import-file }
|
||||
|
||||
Импортируйте `File` и `UploadFile` из модуля `fastapi`:
|
||||
|
||||
{* ../../docs_src/request_files/tutorial001_an_py39.py hl[3] *}
|
||||
|
||||
## Определите параметры `File`
|
||||
## Определите параметры `File` { #define-file-parameters }
|
||||
|
||||
Создайте параметры `File` так же, как вы это делаете для `Body` или `Form`:
|
||||
|
||||
@@ -46,7 +50,7 @@
|
||||
|
||||
Однако возможны случаи, когда использование `UploadFile` может оказаться полезным.
|
||||
|
||||
## Загрузка файла с помощью `UploadFile`
|
||||
## Параметры файла с `UploadFile` { #file-parameters-with-uploadfile }
|
||||
|
||||
Определите параметр файла с типом `UploadFile`:
|
||||
|
||||
@@ -62,7 +66,7 @@
|
||||
* Он реализует <a href="https://docs.python.org/3/glossary.html#term-file-like-object" class="external-link" target="_blank">file-like</a> `async` интерфейс.
|
||||
* Он предоставляет реальный объект Python <a href="https://docs.python.org/3/library/tempfile.html#tempfile.SpooledTemporaryFile" class="external-link" target="_blank">`SpooledTemporaryFile`</a> который вы можете передать непосредственно другим библиотекам, которые ожидают файл в качестве объекта.
|
||||
|
||||
### `UploadFile`
|
||||
### `UploadFile` { #uploadfile }
|
||||
|
||||
`UploadFile` имеет следующие атрибуты:
|
||||
|
||||
@@ -70,12 +74,12 @@
|
||||
* `content_type`: Строка `str` с типом содержимого (MIME type / media type) (например, `image/jpeg`).
|
||||
* `file`: <a href="https://docs.python.org/3/library/tempfile.html#tempfile.SpooledTemporaryFile" class="external-link" target="_blank">`SpooledTemporaryFile`</a> (a <a href="https://docs.python.org/3/glossary.html#term-file-like-object" class="external-link" target="_blank">file-like</a> объект). Это фактический файл Python, который можно передавать непосредственно другим функциям или библиотекам, ожидающим файл в качестве объекта.
|
||||
|
||||
`UploadFile` имеет следующие методы `async`. Все они вызывают соответствующие файловые методы (используя внутренний SpooledTemporaryFile).
|
||||
`UploadFile` имеет следующие методы `async`. Все они вызывают соответствующие файловые методы (используя внутренний `SpooledTemporaryFile`).
|
||||
|
||||
* `write(data)`: Записать данные `data` (`str` или `bytes`) в файл.
|
||||
* `read(size)`: Прочитать количество `size` (`int`) байт/символов из файла.
|
||||
* `seek(offset)`: Перейти к байту на позиции `offset` (`int`) в файле.
|
||||
* Наример, `await myfile.seek(0)` перейдет к началу файла.
|
||||
* Например, `await myfile.seek(0)` перейдет к началу файла.
|
||||
* Это особенно удобно, если вы один раз выполнили команду `await myfile.read()`, а затем вам нужно прочитать содержимое файла еще раз.
|
||||
* `close()`: Закрыть файл.
|
||||
|
||||
@@ -93,6 +97,7 @@ contents = await myfile.read()
|
||||
contents = myfile.file.read()
|
||||
```
|
||||
|
||||
|
||||
/// note | Технические детали `async`
|
||||
|
||||
При использовании методов `async` **FastAPI** запускает файловые методы в пуле потоков и ожидает их.
|
||||
@@ -105,7 +110,7 @@ contents = myfile.file.read()
|
||||
|
||||
///
|
||||
|
||||
## Про данные формы ("Form Data")
|
||||
## Что такое «данные формы» { #what-is-form-data }
|
||||
|
||||
Способ, которым HTML-формы (`<form></form>`) отправляют данные на сервер, обычно использует "специальную" кодировку для этих данных, отличную от JSON.
|
||||
|
||||
@@ -117,7 +122,7 @@ contents = myfile.file.read()
|
||||
|
||||
Но когда форма включает файлы, она кодируется как multipart/form-data. Если вы используете `File`, **FastAPI** будет знать, что ему нужно получить файлы из нужной части тела.
|
||||
|
||||
Если вы хотите узнать больше об этих кодировках и полях форм, перейдите по ссылке <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" class="external-link" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> web docs for <code>POST</code></a>.
|
||||
Если вы хотите узнать больше об этих кодировках и полях форм, перейдите по ссылке <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" class="external-link" target="_blank"><abbr title="Mozilla Developer Network – Сеть разработчиков Mozilla">MDN</abbr> web docs for <code>POST</code></a>.
|
||||
|
||||
///
|
||||
|
||||
@@ -129,19 +134,19 @@ contents = myfile.file.read()
|
||||
|
||||
///
|
||||
|
||||
## Необязательная загрузка файлов
|
||||
## Необязательная загрузка файлов { #optional-file-upload }
|
||||
|
||||
Вы можете сделать загрузку файла необязательной, используя стандартные аннотации типов и установив значение по умолчанию `None`:
|
||||
|
||||
{* ../../docs_src/request_files/tutorial001_02_an_py310.py hl[9,17] *}
|
||||
|
||||
## `UploadFile` с дополнительными метаданными
|
||||
## `UploadFile` с дополнительными метаданными { #uploadfile-with-additional-metadata }
|
||||
|
||||
Вы также можете использовать `File()` вместе с `UploadFile`, например, для установки дополнительных метаданных:
|
||||
|
||||
{* ../../docs_src/request_files/tutorial001_03_an_py39.py hl[9,15] *}
|
||||
|
||||
## Загрузка нескольких файлов
|
||||
## Загрузка нескольких файлов { #multiple-file-uploads }
|
||||
|
||||
Можно одновременно загружать несколько файлов.
|
||||
|
||||
@@ -153,7 +158,7 @@ contents = myfile.file.read()
|
||||
|
||||
Вы получите, как и было объявлено, список `list` из `bytes` или `UploadFile`.
|
||||
|
||||
/// note | Technical Details
|
||||
/// note | Технические детали
|
||||
|
||||
Можно также использовать `from starlette.responses import HTMLResponse`.
|
||||
|
||||
@@ -161,12 +166,12 @@ contents = myfile.file.read()
|
||||
|
||||
///
|
||||
|
||||
### Загрузка нескольких файлов с дополнительными метаданными
|
||||
### Загрузка нескольких файлов с дополнительными метаданными { #multiple-file-uploads-with-additional-metadata }
|
||||
|
||||
Так же, как и раньше, вы можете использовать `File()` для задания дополнительных параметров, даже для `UploadFile`:
|
||||
|
||||
{* ../../docs_src/request_files/tutorial003_an_py39.py hl[11,18:20] *}
|
||||
|
||||
## Резюме
|
||||
## Резюме { #recap }
|
||||
|
||||
Используйте `File`, `bytes` и `UploadFile` для работы с файлами, которые будут загружаться и передаваться в виде данных формы.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Модели форм
|
||||
# Модели форм { #form-models }
|
||||
|
||||
Вы можете использовать **Pydantic-модели** для объявления **полей форм** в FastAPI.
|
||||
Вы можете использовать **Pydantic-модели** для объявления **полей формы** в FastAPI.
|
||||
|
||||
/// info | Дополнительная информация
|
||||
|
||||
@@ -16,11 +16,11 @@ $ pip install python-multipart
|
||||
|
||||
/// note | Заметка
|
||||
|
||||
Этот функционал доступен с версии `0.113.0`. 🤓
|
||||
Этот функционал доступен начиная с версии FastAPI `0.113.0`. 🤓
|
||||
|
||||
///
|
||||
|
||||
## Pydantic-модель для формы
|
||||
## Pydantic-модели для форм { #pydantic-models-for-forms }
|
||||
|
||||
Вам просто нужно объявить **Pydantic-модель** с полями, которые вы хотите получить как **поля формы**, а затем объявить параметр как `Form`:
|
||||
|
||||
@@ -28,21 +28,21 @@ $ pip install python-multipart
|
||||
|
||||
**FastAPI** **извлечёт** данные для **каждого поля** из **данных формы** в запросе и выдаст вам объявленную Pydantic-модель.
|
||||
|
||||
## Проверка сгенерированной документации
|
||||
## Проверьте документацию { #check-the-docs }
|
||||
|
||||
Вы можете посмотреть поля формы в графическом интерфейсе Документации по пути `/docs`:
|
||||
Вы можете проверить это в интерфейсе документации по адресу `/docs`:
|
||||
|
||||
<div class="screenshot">
|
||||
<img src="/img/tutorial/request-form-models/image01.png">
|
||||
</div>
|
||||
|
||||
## Запрет дополнительных полей формы
|
||||
## Запрет дополнительных полей формы { #forbid-extra-form-fields }
|
||||
|
||||
В некоторых случаях (не особо часто встречающихся) вам может понадобиться **ограничить** поля формы только теми, которые объявлены в Pydantic-модели. И **запретить** любые **дополнительные** поля.
|
||||
|
||||
/// note | Заметка
|
||||
|
||||
Этот функционал доступен с версии `0.114.0`. 🤓
|
||||
Этот функционал доступен начиная с версии FastAPI `0.114.0`. 🤓
|
||||
|
||||
///
|
||||
|
||||
@@ -73,6 +73,6 @@ $ pip install python-multipart
|
||||
}
|
||||
```
|
||||
|
||||
## Заключение
|
||||
## Итоги { #summary }
|
||||
|
||||
Вы можете использовать Pydantic-модели для объявления полей форм в FastAPI. 😎
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
# Файлы и формы в запросе
|
||||
# Файлы и формы в запросе { #request-forms-and-files }
|
||||
|
||||
Вы можете определять файлы и поля формы одновременно, используя `File` и `Form`.
|
||||
|
||||
/// info | Дополнительная информация
|
||||
/// info | Информация
|
||||
|
||||
Чтобы получать загруженные файлы и/или данные форм, сначала установите <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>.
|
||||
|
||||
Например: `pip install python-multipart`.
|
||||
Убедитесь, что вы создали [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активировали его, а затем установили пакет, например:
|
||||
|
||||
```console
|
||||
$ pip install python-multipart
|
||||
```
|
||||
|
||||
///
|
||||
|
||||
## Импортируйте `File` и `Form`
|
||||
## Импортируйте `File` и `Form` { #import-file-and-form }
|
||||
|
||||
{* ../../docs_src/request_forms_and_files/tutorial001_an_py39.py hl[3] *}
|
||||
|
||||
## Определите параметры `File` и `Form`
|
||||
## Определите параметры `File` и `Form` { #define-file-and-form-parameters }
|
||||
|
||||
Создайте параметры файла и формы таким же образом, как для `Body` или `Query`:
|
||||
|
||||
@@ -22,16 +26,16 @@
|
||||
|
||||
Файлы и поля формы будут загружены в виде данных формы, и вы получите файлы и поля формы.
|
||||
|
||||
Вы можете объявить некоторые файлы как `bytes`, а некоторые - как `UploadFile`.
|
||||
Вы можете объявить некоторые файлы как `bytes`, а некоторые — как `UploadFile`.
|
||||
|
||||
/// warning | Внимание
|
||||
|
||||
Вы можете объявить несколько параметров `File` и `Form` в операции *path*, но вы не можете также объявить поля `Body`, которые вы ожидаете получить в виде JSON, так как запрос будет иметь тело, закодированное с помощью `multipart/form-data` вместо `application/json`.
|
||||
Вы можете объявить несколько параметров `File` и `Form` в операции пути, но вы не можете также объявить поля `Body`, которые вы ожидаете получить в виде JSON, так как запрос будет иметь тело, закодированное с помощью `multipart/form-data` вместо `application/json`.
|
||||
|
||||
Это не ограничение **Fast API**, это часть протокола HTTP.
|
||||
Это не ограничение **FastAPI**, это часть протокола HTTP.
|
||||
|
||||
///
|
||||
|
||||
## Резюме
|
||||
## Резюме { #recap }
|
||||
|
||||
Используйте `File` и `Form` вместе, когда необходимо получить данные и файлы в одном запросе.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Данные формы
|
||||
# Данные формы { #form-data }
|
||||
|
||||
Когда вам нужно получить поля формы вместо JSON, вы можете использовать `Form`.
|
||||
|
||||
@@ -6,64 +6,68 @@
|
||||
|
||||
Чтобы использовать формы, сначала установите <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>.
|
||||
|
||||
Например, выполните команду `pip install python-multipart`.
|
||||
Убедитесь, что вы создали [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активировали его, а затем установили пакет, например:
|
||||
|
||||
```console
|
||||
$ pip install python-multipart
|
||||
```
|
||||
|
||||
///
|
||||
|
||||
## Импорт `Form`
|
||||
## Импорт `Form` { #import-form }
|
||||
|
||||
Импортируйте `Form` из `fastapi`:
|
||||
|
||||
{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[3] *}
|
||||
|
||||
## Определение параметров `Form`
|
||||
## Определение параметров `Form` { #define-form-parameters }
|
||||
|
||||
Создайте параметры формы так же, как это делается для `Body` или `Query`:
|
||||
|
||||
{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[9] *}
|
||||
|
||||
Например, в одном из способов использования спецификации OAuth2 (называемом "потоком пароля") требуется отправить `username` и `password` в виде полей формы.
|
||||
Например, в одном из способов использования спецификации OAuth2 (называемом «потоком пароля») требуется отправить `username` и `password` в виде полей формы.
|
||||
|
||||
Данный способ требует отправку данных для авторизации посредством формы (а не JSON) и обязательного наличия в форме строго именованных полей `username` и `password`.
|
||||
<abbr title="specification – спецификация">spec</abbr> требует, чтобы поля были строго названы `username` и `password` и отправлялись как поля формы, а не JSON.
|
||||
|
||||
Вы можете настроить `Form` точно так же, как настраиваете и `Body` ( `Query`, `Path`, `Cookie`), включая валидации, примеры, псевдонимы (например, `user-name` вместо `username`) и т.д.
|
||||
С помощью `Form` вы можете объявить те же настройки, что и с `Body` (и `Query`, `Path`, `Cookie`), включая валидацию, примеры, псевдоним (например, `user-name` вместо `username`) и т.д.
|
||||
|
||||
/// info | Дополнительная информация
|
||||
|
||||
`Form` - это класс, который наследуется непосредственно от `Body`.
|
||||
`Form` — это класс, который наследуется непосредственно от `Body`.
|
||||
|
||||
///
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Вам необходимо явно указывать параметр `Form` при объявлении каждого поля, иначе поля будут интерпретироваться как параметры запроса или параметры тела (JSON).
|
||||
Чтобы объявлять данные формы, вам нужно явно использовать `Form`, иначе параметры будут интерпретированы как параметры запроса или параметры тела (JSON).
|
||||
|
||||
///
|
||||
|
||||
## О "полях формы"
|
||||
## О «полях формы» { #about-form-fields }
|
||||
|
||||
Обычно способ, которым HTML-формы (`<form></form>`) отправляют данные на сервер, использует "специальное" кодирование для этих данных, отличное от JSON.
|
||||
Обычно способ, которым HTML-формы (`<form></form>`) отправляют данные на сервер, использует «специальное» кодирование для этих данных, отличное от JSON.
|
||||
|
||||
**FastAPI** гарантирует правильное чтение этих данных из соответствующего места, а не из JSON.
|
||||
**FastAPI** гарантирует, что эти данные будут прочитаны из нужного места, а не из JSON.
|
||||
|
||||
/// note | Технические детали
|
||||
|
||||
Данные из форм обычно кодируются с использованием "типа медиа" `application/x-www-form-urlencoded`.
|
||||
Данные из форм обычно кодируются с использованием «типа содержимого» `application/x-www-form-urlencoded`.
|
||||
|
||||
Но когда форма содержит файлы, она кодируется как `multipart/form-data`. Вы узнаете о работе с файлами в следующей главе.
|
||||
Но когда форма содержит файлы, она кодируется как `multipart/form-data`. О работе с файлами вы прочтёте в следующей главе.
|
||||
|
||||
Если вы хотите узнать больше про кодировки и поля формы, ознакомьтесь с <a href="https://developer.mozilla.org/ru/docs/Web/HTTP/Methods/POST" class="external-link" target="_blank">документацией <abbr title="Mozilla Developer Network">MDN</abbr> для `POST` на веб-сайте</a>.
|
||||
Если вы хотите узнать больше про эти кодировки и поля формы, обратитесь к <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" class="external-link" target="_blank"><abbr title="Mozilla Developer Network – Сеть разработчиков Mozilla">MDN</abbr> веб-документации для `POST`</a>.
|
||||
|
||||
///
|
||||
|
||||
/// warning | Предупреждение
|
||||
|
||||
Вы можете объявлять несколько параметров `Form` в *операции пути*, но вы не можете одновременно с этим объявлять поля `Body`, которые вы ожидаете получить в виде JSON, так как запрос будет иметь тело, закодированное с использованием `application/x-www-form-urlencoded`, а не `application/json`.
|
||||
Вы можете объявлять несколько параметров `Form` в *операции пути*, но вы не можете одновременно объявлять поля `Body`, которые вы ожидаете получить в виде JSON, так как запрос будет иметь тело, закодированное с использованием `application/x-www-form-urlencoded`, а не `application/json`.
|
||||
|
||||
Это не ограничение **FastAPI**, это часть протокола HTTP.
|
||||
|
||||
///
|
||||
|
||||
## Резюме
|
||||
## Резюме { #recap }
|
||||
|
||||
Используйте `Form` для объявления входных параметров данных формы.
|
||||
|
||||
@@ -1,237 +1,247 @@
|
||||
# Модель ответа - Возвращаемый тип
|
||||
# Модель ответа — Возвращаемый тип { #response-model-return-type }
|
||||
|
||||
Вы можете объявить тип ответа, указав аннотацию **возвращаемого значения** для *функции операции пути*.
|
||||
Вы можете объявить тип, используемый для ответа, указав аннотацию **возвращаемого значения** для *функции-обработчика пути*.
|
||||
|
||||
FastAPI позволяет использовать **аннотации типов** таким же способом, как и для ввода данных в **параметры** функции, вы можете использовать модели Pydantic, списки, словари, скалярные типы (такие, как int, bool и т.д.).
|
||||
Вы можете использовать **аннотации типов** так же, как и для входных данных в **параметрах** функции: Pydantic-модели, списки, словари, скалярные значения (целые числа, булевы и т.д.).
|
||||
|
||||
{* ../../docs_src/response_model/tutorial001_01_py310.py hl[16,21] *}
|
||||
|
||||
FastAPI будет использовать этот возвращаемый тип для:
|
||||
FastAPI будет использовать этот тип ответа для:
|
||||
|
||||
* **Валидации** ответа.
|
||||
* Если данные невалидны (например, отсутствует одно из полей), это означает, что код *вашего* приложения работает некорректно и функция возвращает не то, что вы ожидаете. В таком случае приложение вернет server error вместо того, чтобы отправить неправильные данные. Таким образом, вы и ваши пользователи можете быть уверены, что получите корректные данные в том виде, в котором они ожидаются.
|
||||
* Добавьте **JSON схему** для ответа внутри *операции пути* OpenAPI.
|
||||
* Она будет использована для **автоматически генерируемой документации**.
|
||||
* А также - для автоматической кодогенерации пользователями.
|
||||
* **Валидации** возвращаемых данных.
|
||||
* Если данные невалидны (например, отсутствует поле), это означает, что код *вашего* приложения работает некорректно и возвращает не то, что должен. В таком случае будет возвращена ошибка сервера вместо неправильных данных. Так вы и ваши клиенты можете быть уверены, что получите ожидаемые данные и ожидаемую структуру.
|
||||
* Добавления **JSON Schema** для ответа в OpenAPI *операции пути*.
|
||||
* Это будет использовано **автоматической документацией**.
|
||||
* Это также будет использовано инструментами автоматической генерации клиентского кода.
|
||||
|
||||
Но самое важное:
|
||||
Но самое главное:
|
||||
|
||||
* Ответ будет **ограничен и отфильтрован** - т.е. в нем останутся только те данные, которые определены в возвращаемом типе.
|
||||
* Это особенно важно для **безопасности**, далее мы рассмотрим эту тему подробнее.
|
||||
* Выходные данные будут **ограничены и отфильтрованы** в соответствии с тем, что определено в возвращаемом типе.
|
||||
* Это особенно важно для **безопасности**, ниже мы рассмотрим это подробнее.
|
||||
|
||||
## Параметр `response_model`
|
||||
## Параметр `response_model` { #response-model-parameter }
|
||||
|
||||
Бывают случаи, когда вам необходимо (или просто хочется) возвращать данные, которые не полностью соответствуют объявленному типу.
|
||||
Бывают случаи, когда вам нужно или хочется возвращать данные, которые не в точности соответствуют объявленному типу.
|
||||
|
||||
Допустим, вы хотите, чтобы ваша функция **возвращала словарь (dict)** или объект из базы данных, но при этом **объявляете выходной тип как модель Pydantic**. Тогда именно указанная модель будет использована для автоматической документации, валидации и т.п. для объекта, который вы вернули (например, словаря или объекта из базы данных).
|
||||
Например, вы можете хотеть **возвращать словарь (dict)** или объект из базы данных, но **объявить его как Pydantic-модель**. Тогда Pydantic-модель выполнит документирование данных, валидацию и т.п. для объекта, который вы вернули (например, словаря или объекта из базы данных).
|
||||
|
||||
Но если указать аннотацию возвращаемого типа, статическая проверка типов будет выдавать ошибку (абсолютно корректную в данном случае). Она будет говорить о том, что ваша функция должна возвращать данные одного типа (например, dict), а в аннотации вы объявили другой тип (например, модель Pydantic).
|
||||
Если вы добавите аннотацию возвращаемого типа, инструменты и редакторы кода начнут жаловаться (и будут правы), что функция возвращает тип (например, dict), отличный от объявленного (например, Pydantic-модель).
|
||||
|
||||
В таком случае можно использовать параметр `response_model` внутри *декоратора операции пути* вместо аннотации возвращаемого значения функции.
|
||||
В таких случаях вместо аннотации возвращаемого типа можно использовать параметр `response_model` у *декоратора операции пути*.
|
||||
|
||||
Параметр `response_model` может быть указан для любой *операции пути*:
|
||||
Параметр `response_model` можно указать у любой *операции пути*:
|
||||
|
||||
* `@app.get()`
|
||||
* `@app.post()`
|
||||
* `@app.put()`
|
||||
* `@app.delete()`
|
||||
* и др.
|
||||
* и т.д.
|
||||
|
||||
{* ../../docs_src/response_model/tutorial001_py310.py hl[17,22,24:27] *}
|
||||
|
||||
/// note | Технические детали
|
||||
/// note | Примечание
|
||||
|
||||
Помните, что параметр `response_model` является параметром именно декоратора http-методов (`get`, `post`, и т.п.). Не следует его указывать для *функций операций пути*, как вы бы поступили с другими параметрами или с телом запроса.
|
||||
Обратите внимание, что `response_model` — это параметр метода «декоратора» (`get`, `post` и т.д.), а не вашей *функции-обработчика пути*, в которой указываются параметры и тело запроса.
|
||||
|
||||
///
|
||||
|
||||
`response_model` принимает те же типы, которые можно указать для какого-либо поля в модели Pydantic. Таким образом, это может быть как одиночная модель Pydantic, так и `список (list)` моделей Pydantic. Например, `List[Item]`.
|
||||
`response_model` принимает тот же тип, что вы бы объявили для поля Pydantic-модели, то есть это может быть одна Pydantic-модель, а может быть, например, `list` Pydantic-моделей, как `List[Item]`.
|
||||
|
||||
FastAPI будет использовать значение `response_model` для того, чтобы автоматически генерировать документацию, производить валидацию и т.п. А также для **конвертации и фильтрации выходных данных** в объявленный тип.
|
||||
FastAPI будет использовать `response_model` для документации, валидации и т. п., а также для **конвертации и фильтрации выходных данных** к объявленному типу.
|
||||
|
||||
/// tip | Подсказка
|
||||
/// tip | Совет
|
||||
|
||||
Если вы используете анализаторы типов со строгой проверкой (например, mypy), можно указать `Any` в качестве типа возвращаемого значения функции.
|
||||
Если у вас в редакторе кода, mypy и т. п. включены строгие проверки типов, вы можете объявить возвращаемый тип функции как `Any`.
|
||||
|
||||
Таким образом вы информируете ваш редактор кода, что намеренно возвращаете данные неопределенного типа. Но возможности FastAPI, такие как автоматическая генерация документации, валидация, фильтрация и т.д. все так же будут работать, просто используя параметр `response_model`.
|
||||
Так вы сообщите редактору, что намеренно возвращаете что угодно. Но FastAPI всё равно выполнит документацию данных, валидацию, фильтрацию и т.д. с помощью `response_model`.
|
||||
|
||||
///
|
||||
|
||||
### Приоритет `response_model`
|
||||
### Приоритет `response_model` { #response-model-priority }
|
||||
|
||||
Если одновременно указать аннотацию типа для ответа функции и параметр `response_model` - последний будет иметь больший приоритет и FastAPI будет использовать именно его.
|
||||
Если вы объявите и возвращаемый тип, и `response_model`, приоритет будет у `response_model`, именно его использует FastAPI.
|
||||
|
||||
Таким образом вы можете объявить корректные аннотации типов к вашим функциям, даже если они возвращают тип, отличающийся от указанного в `response_model`. Они будут считаны во время статической проверки типов вашими помощниками, например, mypy. При этом вы все так же используете возможности FastAPI для автоматической документации, валидации и т.д. благодаря `response_model`.
|
||||
Так вы можете добавить корректные аннотации типов к своим функциям, даже если фактически возвращаете тип, отличный от модели ответа, чтобы ими пользовались редактор и инструменты вроде mypy. И при этом FastAPI продолжит выполнять валидацию данных, документацию и т.д. с использованием `response_model`.
|
||||
|
||||
Вы можете указать значение `response_model=None`, чтобы отключить создание модели ответа для данной *операции пути*. Это может понадобиться, если вы добавляете аннотации типов для данных, не являющихся валидными полями Pydantic. Мы увидим пример кода для такого случая в одном из разделов ниже.
|
||||
Вы также можете указать `response_model=None`, чтобы отключить создание модели ответа для данной *операции пути*. Это может понадобиться, если вы добавляете аннотации типов для вещей, не являющихся валидными полями Pydantic. Пример вы увидите ниже.
|
||||
|
||||
## Получить и вернуть один и тот же тип данных
|
||||
## Вернуть те же входные данные { #return-the-same-input-data }
|
||||
|
||||
Здесь мы объявили модель `UserIn`, которая хранит пользовательский пароль в открытом виде:
|
||||
Здесь мы объявляем модель `UserIn`, она будет содержать пароль в открытом виде:
|
||||
|
||||
{* ../../docs_src/response_model/tutorial002_py310.py hl[7,9] *}
|
||||
|
||||
/// info | Информация
|
||||
|
||||
Чтобы использовать `EmailStr`, прежде необходимо установить <a href="https://github.com/JoshData/python-email-validator" class="external-link" target="_blank">`email-validator`</a>.
|
||||
Используйте `pip install email-validator`
|
||||
или `pip install pydantic[email]`.
|
||||
Чтобы использовать `EmailStr`, сначала установите <a href="https://github.com/JoshData/python-email-validator" class="external-link" target="_blank">`email-validator`</a>.
|
||||
|
||||
Создайте [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активируйте его и затем установите пакет, например:
|
||||
|
||||
```console
|
||||
$ pip install email-validator
|
||||
```
|
||||
|
||||
или так:
|
||||
|
||||
```console
|
||||
$ pip install "pydantic[email]"
|
||||
```
|
||||
|
||||
///
|
||||
|
||||
Далее мы используем нашу модель в аннотациях типа как для аргумента функции, так и для выходного значения:
|
||||
И мы используем эту модель для объявления входных данных, и ту же модель — для объявления выходных данных:
|
||||
|
||||
{* ../../docs_src/response_model/tutorial002_py310.py hl[16] *}
|
||||
|
||||
Теперь всякий раз, когда клиент создает пользователя с паролем, API будет возвращать его пароль в ответе.
|
||||
Теперь, когда браузер создаёт пользователя с паролем, API вернёт тот же пароль в ответе.
|
||||
|
||||
В данном случае это не такая уж большая проблема, поскольку ответ получит тот же самый пользователь, который и создал пароль.
|
||||
В этом случае это может быть не проблемой, так как пароль отправляет тот же пользователь.
|
||||
|
||||
Но что если мы захотим использовать эту модель для какой-либо другой *операции пути*? Мы можем, сами того не желая, отправить пароль любому другому пользователю.
|
||||
Но если мы используем ту же модель в другой *операции пути*, мы можем начать отправлять пароли пользователей каждому клиенту.
|
||||
|
||||
/// danger | Осторожно
|
||||
|
||||
Никогда не храните пароли пользователей в открытом виде, а также никогда не возвращайте их в ответе, как в примере выше. В противном случае - убедитесь, что вы хорошо продумали и учли все возможные риски такого подхода и вам известно, что вы делаете.
|
||||
Никогда не храните пароль пользователя в открытом виде и не отправляйте его в ответе подобным образом, если только вы не понимаете всех рисков и точно знаете, что делаете.
|
||||
|
||||
///
|
||||
|
||||
## Создание модели для ответа
|
||||
## Добавить модель для ответа { #add-an-output-model }
|
||||
|
||||
Вместо этого мы можем создать входную модель, хранящую пароль в открытом виде и выходную модель без пароля:
|
||||
Вместо этого мы можем создать входную модель с паролем в открытом виде и выходную модель без него:
|
||||
|
||||
{* ../../docs_src/response_model/tutorial003_py310.py hl[9,11,16] *}
|
||||
|
||||
В таком случае, даже несмотря на то, что наша *функция операции пути* возвращает тот же самый объект пользователя с паролем, полученным на вход:
|
||||
Здесь, хотя *функция-обработчик пути* возвращает тот же входной объект пользователя, содержащий пароль:
|
||||
|
||||
{* ../../docs_src/response_model/tutorial003_py310.py hl[24] *}
|
||||
|
||||
...мы указали в `response_model` модель `UserOut`, в которой отсутствует поле, содержащее пароль - и он будет исключен из ответа:
|
||||
...мы объявили `response_model` как модель `UserOut`, в которой нет пароля:
|
||||
|
||||
{* ../../docs_src/response_model/tutorial003_py310.py hl[22] *}
|
||||
|
||||
Таким образом **FastAPI** позаботится о фильтрации ответа и исключит из него всё, что не указано в выходной модели (при помощи Pydantic).
|
||||
Таким образом, **FastAPI** позаботится о том, чтобы отфильтровать все данные, не объявленные в выходной модели (используя Pydantic).
|
||||
|
||||
### `response_model` или возвращаемый тип данных
|
||||
### `response_model` или возвращаемый тип { #response-model-or-return-type }
|
||||
|
||||
В нашем примере модели входных данных и выходных данных различаются. И если мы укажем аннотацию типа выходного значения функции как `UserOut` - проверка типов выдаст ошибку из-за того, что мы возвращаем некорректный тип. Поскольку это 2 разных класса.
|
||||
В этом случае, поскольку две модели различаются, если бы мы аннотировали возвращаемый тип функции как `UserOut`, редактор и инструменты пожаловались бы, что мы возвращаем неверный тип, так как это разные классы.
|
||||
|
||||
Поэтому в нашем примере мы можем объявить тип ответа только в параметре `response_model`.
|
||||
Поэтому в этом примере мы должны объявить тип ответа в параметре `response_model`.
|
||||
|
||||
...но продолжайте читать дальше, чтобы узнать как можно это обойти.
|
||||
...но читайте дальше, чтобы узнать, как это обойти.
|
||||
|
||||
## Возвращаемый тип и Фильтрация данных
|
||||
## Возвращаемый тип и фильтрация данных { #return-type-and-data-filtering }
|
||||
|
||||
Продолжим рассматривать предыдущий пример. Мы хотели **аннотировать входные данные одним типом**, а выходное значение - **другим типом**.
|
||||
Продолжим предыдущий пример. Мы хотели **аннотировать функцию одним типом**, но при этом иметь возможность вернуть из функции что-то, что фактически включает **больше данных**.
|
||||
|
||||
Мы хотим, чтобы FastAPI продолжал **фильтровать** данные, используя `response_model`.
|
||||
Мы хотим, чтобы FastAPI продолжал **фильтровать** данные с помощью модели ответа. Так что, даже если функция возвращает больше данных, в ответ будут включены только поля, объявленные в модели ответа.
|
||||
|
||||
В прошлом примере, т.к. входной и выходной типы являлись разными классами, мы были вынуждены использовать параметр `response_model`. И как следствие, мы лишались помощи статических анализаторов для проверки ответа функции.
|
||||
В предыдущем примере, поскольку классы были разными, нам пришлось использовать параметр `response_model`. Но это также означает, что мы теряем поддержку от редактора и инструментов, проверяющих возвращаемый тип функции.
|
||||
|
||||
Но в подавляющем большинстве случаев мы будем хотеть, чтобы модель ответа лишь **фильтровала/удаляла** некоторые данные из ответа, как в нашем примере.
|
||||
Однако в большинстве таких случаев нам нужно лишь **отфильтровать/убрать** некоторые данные, как в этом примере.
|
||||
|
||||
И в таких случаях мы можем использовать классы и наследование, чтобы пользоваться преимуществами **аннотаций типов** и получать более полную статическую проверку типов. Но при этом все так же получать **фильтрацию ответа** от FastAPI.
|
||||
И в этих случаях мы можем использовать классы и наследование, чтобы воспользоваться **аннотациями типов** функций для лучшей поддержки в редакторе и инструментах и при этом получить **фильтрацию данных** от FastAPI.
|
||||
|
||||
{* ../../docs_src/response_model/tutorial003_01_py310.py hl[7:10,13:14,18] *}
|
||||
|
||||
Таким образом, мы получаем поддержку редактора кода и mypy в части типов, сохраняя при этом фильтрацию данных от FastAPI.
|
||||
Так мы получаем поддержку инструментов (редакторы, mypy) — код корректен с точки зрения типов — и одновременно получаем фильтрацию данных от FastAPI.
|
||||
|
||||
Как это возможно? Давайте разберемся. 🤓
|
||||
Как это работает? Давайте разберёмся. 🤓
|
||||
|
||||
### Аннотации типов и инструменты для их проверки
|
||||
### Аннотации типов и инструменты { #type-annotations-and-tooling }
|
||||
|
||||
Для начала давайте рассмотрим как наш редактор кода, mypy и другие помощники разработчика видят аннотации типов.
|
||||
Сначала посмотрим, как это увидят редакторы, mypy и другие инструменты.
|
||||
|
||||
У модели `BaseUser` есть некоторые поля. Затем `UserIn` наследуется от `BaseUser` и добавляет новое поле `password`. Таким образом модель будет включать в себя все поля из первой модели (родителя), а также свои собственные.
|
||||
`BaseUser` содержит базовые поля. Затем `UserIn` наследуется от `BaseUser` и добавляет поле `password`, то есть он включает все поля обеих моделей.
|
||||
|
||||
Мы аннотируем возвращаемый тип функции как `BaseUser`, но фактически мы будем возвращать объект типа `UserIn`.
|
||||
Мы аннотируем возвращаемый тип функции как `BaseUser`, но фактически возвращаем экземпляр `UserIn`.
|
||||
|
||||
Редакторы, mypy и другие инструменты не будут иметь возражений против такого подхода, поскольку `UserIn` является подклассом `BaseUser`. Это означает, что такой тип будет *корректным*, т.к. ответ может быть чем угодно, если это будет `BaseUser`.
|
||||
Редактор, mypy и другие инструменты не будут возражать, потому что с точки зрения типов `UserIn` — подкласс `BaseUser`, что означает, что это *валидный* тип везде, где ожидается что-то, являющееся `BaseUser`.
|
||||
|
||||
### Фильтрация Данных FastAPI
|
||||
### Фильтрация данных FastAPI { #fastapi-data-filtering }
|
||||
|
||||
FastAPI знает тип ответа функции, так что вы можете быть уверены, что на выходе будут **только** те поля, которые вы указали.
|
||||
Теперь, для FastAPI: он увидит возвращаемый тип и убедится, что то, что вы возвращаете, включает **только** поля, объявленные в этом типе.
|
||||
|
||||
FastAPI совместно с Pydantic выполнит некоторую магию "под капотом", чтобы убедиться, что те же самые правила наследования классов не используются для фильтрации возвращаемых данных, в противном случае вы могли бы в конечном итоге вернуть гораздо больше данных, чем ожидали.
|
||||
FastAPI делает несколько вещей внутри вместе с Pydantic, чтобы гарантировать, что те же правила наследования классов не используются для фильтрации возвращаемых данных, иначе вы могли бы вернуть гораздо больше данных, чем ожидали.
|
||||
|
||||
Таким образом, вы можете получить все самое лучшее из обоих миров: аннотации типов с **поддержкой инструментов для разработки** и **фильтрацию данных**.
|
||||
Таким образом вы получаете лучшее из обоих миров: аннотации типов с **поддержкой инструментов** и **фильтрацию данных**.
|
||||
|
||||
## Автоматическая документация
|
||||
## Посмотреть в документации { #see-it-in-the-docs }
|
||||
|
||||
Если посмотреть на сгенерированную документацию, вы можете убедиться, что в ней присутствуют обе JSON схемы - как для входной модели, так и для выходной:
|
||||
В автоматической документации вы увидите, что у входной и выходной моделей есть свои JSON Schema:
|
||||
|
||||
<img src="/img/tutorial/response-model/image01.png">
|
||||
|
||||
И также обе модели будут использованы в интерактивной документации API:
|
||||
И обе модели используются в интерактивной документации API:
|
||||
|
||||
<img src="/img/tutorial/response-model/image02.png">
|
||||
|
||||
## Другие аннотации типов
|
||||
## Другие аннотации возвращаемых типов { #other-return-type-annotations }
|
||||
|
||||
Бывают случаи, когда вы возвращаете что-то, что не является валидным типом для Pydantic и вы указываете аннотацию ответа функции только для того, чтобы работала поддержка различных инструментов (редактор кода, mypy и др.).
|
||||
Бывают случаи, когда вы возвращаете что-то, что не является валидным полем Pydantic, и аннотируете это в функции только ради поддержки инструментов (редактор, mypy и т. д.).
|
||||
|
||||
### Возвращаем Response
|
||||
### Возврат Response напрямую { #return-a-response-directly }
|
||||
|
||||
Самый частый сценарий использования - это [возвращать Response напрямую, как описано в расширенной документации](../advanced/response-directly.md){.internal-link target=_blank}.
|
||||
Самый распространённый случай — [возвращать Response напрямую, как описано далее в разделах для продвинутых](../advanced/response-directly.md){.internal-link target=_blank}.
|
||||
|
||||
{* ../../docs_src/response_model/tutorial003_02.py hl[8,10:11] *}
|
||||
|
||||
Это поддерживается FastAPI по-умолчанию, т.к. аннотация проставлена в классе (или подклассе) `Response`.
|
||||
Этот простой случай обрабатывается FastAPI автоматически, потому что аннотация возвращаемого типа — это класс (или подкласс) `Response`.
|
||||
|
||||
И ваши помощники разработки также будут счастливы, т.к. оба класса `RedirectResponse` и `JSONResponse` являются подклассами `Response`. Таким образом мы получаем корректную аннотацию типа.
|
||||
И инструменты тоже будут довольны, потому что и `RedirectResponse`, и `JSONResponse` являются подклассами `Response`, так что аннотация типа корректна.
|
||||
|
||||
### Подкласс Response в аннотации типа
|
||||
### Аннотировать подкласс Response { #annotate-a-response-subclass }
|
||||
|
||||
Вы также можете указать подкласс `Response` в аннотации типа:
|
||||
Вы также можете использовать подкласс `Response` в аннотации типа:
|
||||
|
||||
{* ../../docs_src/response_model/tutorial003_03.py hl[8:9] *}
|
||||
|
||||
Это сработает, потому что `RedirectResponse` является подклассом `Response` и FastAPI автоматически обработает этот простейший случай.
|
||||
Это тоже сработает, так как `RedirectResponse` — подкласс `Response`, и FastAPI автоматически обработает этот случай.
|
||||
|
||||
### Некорректные аннотации типов
|
||||
### Некорректные аннотации возвращаемых типов { #invalid-return-type-annotations }
|
||||
|
||||
Но когда вы возвращаете какой-либо другой произвольный объект, который не является допустимым типом Pydantic (например, объект из базы данных), и вы аннотируете его подобным образом для функции, FastAPI попытается создать из этого типа модель Pydantic и потерпит неудачу.
|
||||
Но когда вы возвращаете произвольный объект, не являющийся валидным типом Pydantic (например, объект базы данных), и аннотируете его таким образом в функции, FastAPI попытается создать модель ответа Pydantic из этой аннотации типа и потерпит неудачу.
|
||||
|
||||
То же самое произошло бы, если бы у вас было что-то вроде <abbr title='Union разных типов буквально означает "любой из перечисленных типов".'>Union</abbr> различных типов и один или несколько из них не являлись бы допустимыми типами для Pydantic. Например, такой вариант приведет к ошибке 💥:
|
||||
То же произойдёт, если у вас будет что-то вроде <abbr title='Объединение нескольких типов означает «любой из этих типов».'>union</abbr> разных типов, где один или несколько не являются валидными типами Pydantic, например, это приведёт к ошибке 💥:
|
||||
|
||||
{* ../../docs_src/response_model/tutorial003_04_py310.py hl[8] *}
|
||||
|
||||
...такой код вызовет ошибку, потому что в аннотации указан неподдерживаемый Pydantic тип. А также этот тип не является классом или подклассом `Response`.
|
||||
...это не сработает, потому что аннотация типа не является типом Pydantic и это не единственный класс `Response` или его подкласс, а объединение (`union`) из `Response` и `dict`.
|
||||
|
||||
### Возможно ли отключить генерацию модели ответа?
|
||||
### Отключить модель ответа { #disable-response-model }
|
||||
|
||||
Продолжим рассматривать предыдущий пример. Допустим, что вы хотите отказаться от автоматической валидации ответа, документации, фильтрации и т.д.
|
||||
Продолжая пример выше, вы можете не хотеть использовать стандартную валидацию данных, документацию, фильтрацию и т.д., выполняемые FastAPI.
|
||||
|
||||
Но в то же время, хотите сохранить аннотацию возвращаемого типа для функции, чтобы обеспечить работу помощников и анализаторов типов (например, mypy).
|
||||
Но при этом вы можете хотеть сохранить аннотацию возвращаемого типа в функции, чтобы пользоваться поддержкой инструментов (редакторы, проверки типов вроде mypy).
|
||||
|
||||
В таком случае, вы можете отключить генерацию модели ответа, указав `response_model=None`:
|
||||
В этом случае вы можете отключить генерацию модели ответа, установив `response_model=None`:
|
||||
|
||||
{* ../../docs_src/response_model/tutorial003_05_py310.py hl[7] *}
|
||||
|
||||
Тогда FastAPI не станет генерировать модель ответа и вы сможете сохранить такую аннотацию типа, которая вам требуется, никак не влияя на работу FastAPI. 🤓
|
||||
Так FastAPI пропустит генерацию модели ответа, и вы сможете использовать любые аннотации возвращаемых типов, не влияя на ваше приложение FastAPI. 🤓
|
||||
|
||||
## Параметры модели ответа
|
||||
## Параметры кодирования модели ответа { #response-model-encoding-parameters }
|
||||
|
||||
Модель ответа может иметь значения по умолчанию, например:
|
||||
У вашей модели ответа могут быть значения по умолчанию, например:
|
||||
|
||||
{* ../../docs_src/response_model/tutorial004_py310.py hl[9,11:12] *}
|
||||
|
||||
* `description: Union[str, None] = None` (или `str | None = None` в Python 3.10), где `None` является значением по умолчанию.
|
||||
* `tax: float = 10.5`, где `10.5` является значением по умолчанию.
|
||||
* `tags: List[str] = []`, где пустой список `[]` является значением по умолчанию.
|
||||
* `description: Union[str, None] = None` (или `str | None = None` в Python 3.10) имеет значение по умолчанию `None`.
|
||||
* `tax: float = 10.5` имеет значение по умолчанию `10.5`.
|
||||
* `tags: List[str] = []` имеет значение по умолчанию пустого списка: `[]`.
|
||||
|
||||
но вы, возможно, хотели бы исключить их из ответа, если данные поля не были заданы явно.
|
||||
но вы можете захотеть опустить их в результате, если они фактически не были сохранены.
|
||||
|
||||
Например, у вас есть модель с множеством необязательных полей в NoSQL базе данных, но вы не хотите отправлять в качестве ответа очень длинный JSON с множеством значений по умолчанию.
|
||||
Например, если у вас есть модели с множеством необязательных атрибутов в NoSQL-базе данных, но вы не хотите отправлять очень длинные JSON-ответы, заполненные значениями по умолчанию.
|
||||
|
||||
### Используйте параметр `response_model_exclude_unset`
|
||||
### Используйте параметр `response_model_exclude_unset` { #use-the-response-model-exclude-unset-parameter }
|
||||
|
||||
Установите для *декоратора операции пути* параметр `response_model_exclude_unset=True`:
|
||||
Вы можете установить у *декоратора операции пути* параметр `response_model_exclude_unset=True`:
|
||||
|
||||
{* ../../docs_src/response_model/tutorial004_py310.py hl[22] *}
|
||||
|
||||
и тогда значения по умолчанию не будут включены в ответ. В нем будут только те поля, значения которых фактически были установлены.
|
||||
и эти значения по умолчанию не будут включены в ответ — только те значения, которые действительно были установлены.
|
||||
|
||||
Итак, если вы отправите запрос на данную *операцию пути* для элемента, с ID = `Foo` - ответ (с исключенными значениями по-умолчанию) будет таким:
|
||||
Итак, если вы отправите запрос к этой *операции пути* для элемента с ID `foo`, ответ (без значений по умолчанию) будет таким:
|
||||
|
||||
```JSON
|
||||
{
|
||||
@@ -242,7 +252,15 @@ FastAPI совместно с Pydantic выполнит некоторую ма
|
||||
|
||||
/// info | Информация
|
||||
|
||||
"Под капотом" FastAPI использует метод `.dict()` у объектов моделей Pydantic <a href="https://docs.pydantic.dev/latest/concepts/serialization/#modeldict" class="external-link" target="_blank">с параметром `exclude_unset`</a>, чтобы достичь такого эффекта.
|
||||
В Pydantic v1 метод назывался `.dict()`, в Pydantic v2 он был помечен как устаревший (но всё ещё поддерживается) и переименован в `.model_dump()`.
|
||||
|
||||
Примеры здесь используют `.dict()` для совместимости с Pydantic v1, но если вы используете Pydantic v2, применяйте `.model_dump()`.
|
||||
|
||||
///
|
||||
|
||||
/// info | Информация
|
||||
|
||||
FastAPI использует метод `.dict()` у Pydantic-моделей с <a href="https://docs.pydantic.dev/1.10/usage/exporting_models/#modeldict" class="external-link" target="_blank">параметром `exclude_unset`</a>, чтобы добиться такого поведения.
|
||||
|
||||
///
|
||||
|
||||
@@ -253,13 +271,13 @@ FastAPI совместно с Pydantic выполнит некоторую ма
|
||||
* `response_model_exclude_defaults=True`
|
||||
* `response_model_exclude_none=True`
|
||||
|
||||
как описано в <a href="https://docs.pydantic.dev/latest/concepts/serialization/#modeldict" class="external-link" target="_blank">документации Pydantic</a> для параметров `exclude_defaults` и `exclude_none`.
|
||||
как описано в <a href="https://docs.pydantic.dev/1.10/usage/exporting_models/#modeldict" class="external-link" target="_blank">документации Pydantic</a> для `exclude_defaults` и `exclude_none`.
|
||||
|
||||
///
|
||||
|
||||
#### Если значение поля отличается от значения по-умолчанию
|
||||
#### Данные со значениями для полей, имеющих значения по умолчанию { #data-with-values-for-fields-with-defaults }
|
||||
|
||||
Если для некоторых полей модели, имеющих значения по-умолчанию, значения были явно установлены - как для элемента с ID = `Bar`, ответ будет таким:
|
||||
Но если в ваших данных есть значения для полей модели, для которых указаны значения по умолчанию, как у элемента с ID `bar`:
|
||||
|
||||
```Python hl_lines="3 5"
|
||||
{
|
||||
@@ -270,11 +288,11 @@ FastAPI совместно с Pydantic выполнит некоторую ма
|
||||
}
|
||||
```
|
||||
|
||||
они не будут исключены из ответа.
|
||||
они будут включены в ответ.
|
||||
|
||||
#### Если значение поля совпадает с его значением по умолчанию
|
||||
#### Данные с такими же значениями, как значения по умолчанию { #data-with-the-same-values-as-the-defaults }
|
||||
|
||||
Если данные содержат те же значения, которые являются для этих полей по умолчанию, но были установлены явно - как для элемента с ID = `baz`, ответ будет таким:
|
||||
Если данные имеют те же значения, что и значения по умолчанию, как у элемента с ID `baz`:
|
||||
|
||||
```Python hl_lines="3 5-6"
|
||||
{
|
||||
@@ -286,54 +304,54 @@ FastAPI совместно с Pydantic выполнит некоторую ма
|
||||
}
|
||||
```
|
||||
|
||||
FastAPI достаточно умен (на самом деле, это заслуга Pydantic), чтобы понять, что, хотя `description`, `tax` и `tags` хранят такие же данные, какие должны быть по умолчанию - для них эти значения были установлены явно (а не получены из значений по умолчанию).
|
||||
FastAPI достаточно умен (на самом деле, это Pydantic), чтобы понять, что хотя `description`, `tax` и `tags` совпадают со значениями по умолчанию, они были установлены явно (а не взяты из значений по умолчанию).
|
||||
|
||||
И поэтому, они также будут включены в JSON ответа.
|
||||
Поэтому они тоже будут включены в JSON-ответ.
|
||||
|
||||
/// tip | Подсказка
|
||||
/// tip | Совет
|
||||
|
||||
Значением по умолчанию может быть что угодно, не только `None`.
|
||||
Обратите внимание, что значения по умолчанию могут быть любыми, не только `None`.
|
||||
|
||||
Им может быть и список (`[]`), значение 10.5 типа `float`, и т.п.
|
||||
Это может быть список (`[]`), число с плавающей точкой `10.5` и т. д.
|
||||
|
||||
///
|
||||
|
||||
### `response_model_include` и `response_model_exclude`
|
||||
### `response_model_include` и `response_model_exclude` { #response-model-include-and-response-model-exclude }
|
||||
|
||||
Вы также можете использовать параметры *декоратора операции пути*, такие, как `response_model_include` и `response_model_exclude`.
|
||||
Вы также можете использовать параметры *декоратора операции пути* `response_model_include` и `response_model_exclude`.
|
||||
|
||||
Они принимают аргументы типа `set`, состоящий из строк (`str`) с названиями атрибутов, которые либо требуется включить в ответ (при этом исключив все остальные), либо наоборот исключить (оставив в ответе все остальные поля).
|
||||
Они принимают `set` из `str` с именами атрибутов, которые нужно включить (исключив остальные) или исключить (оставив остальные).
|
||||
|
||||
Это можно использовать как быстрый способ исключить данные из ответа, не создавая отдельную модель Pydantic.
|
||||
Это можно использовать как быстрый способ, если у вас только одна Pydantic-модель и вы хотите убрать часть данных из ответа.
|
||||
|
||||
/// tip | Подсказка
|
||||
/// tip | Совет
|
||||
|
||||
Но по-прежнему рекомендуется следовать изложенным выше советам и использовать несколько моделей вместо данных параметров.
|
||||
Но всё же рекомендуется использовать подходы выше — несколько классов — вместо этих параметров.
|
||||
|
||||
Потому как JSON схема OpenAPI, генерируемая вашим приложением (а также документация) все еще будет содержать все поля, даже если вы использовали `response_model_include` или `response_model_exclude` и исключили некоторые атрибуты.
|
||||
Потому что JSON Schema, генерируемая в OpenAPI вашего приложения (и документации), всё равно будет соответствовать полной модели, даже если вы используете `response_model_include` или `response_model_exclude`, чтобы опустить некоторые атрибуты.
|
||||
|
||||
То же самое применимо к параметру `response_model_by_alias`.
|
||||
То же относится к `response_model_by_alias`, который работает аналогично.
|
||||
|
||||
///
|
||||
|
||||
{* ../../docs_src/response_model/tutorial005_py310.py hl[29,35] *}
|
||||
|
||||
/// tip | Подсказка
|
||||
/// tip | Совет
|
||||
|
||||
При помощи кода `{"name","description"}` создается объект множества (`set`) с двумя строковыми значениями.
|
||||
Синтаксис `{"name", "description"}` создаёт `set` с этими двумя значениями.
|
||||
|
||||
Того же самого можно достичь используя `set(["name", "description"])`.
|
||||
Это эквивалентно `set(["name", "description"])`.
|
||||
|
||||
///
|
||||
|
||||
#### Что если использовать `list` вместо `set`?
|
||||
#### Использование `list` вместо `set` { #using-lists-instead-of-sets }
|
||||
|
||||
Если вы забыли про `set` и использовали структуру `list` или `tuple`, FastAPI автоматически преобразует этот объект в `set`, чтобы обеспечить корректную работу:
|
||||
Если вы забыли использовать `set` и применили `list` или `tuple`, FastAPI всё равно преобразует это в `set`, и всё будет работать корректно:
|
||||
|
||||
{* ../../docs_src/response_model/tutorial006_py310.py hl[29,35] *}
|
||||
|
||||
## Резюме
|
||||
## Резюме { #recap }
|
||||
|
||||
Используйте параметр `response_model` у *декоратора операции пути* для того, чтобы задать модель ответа и в большей степени для того, чтобы быть уверенным, что приватная информация будет отфильтрована.
|
||||
Используйте параметр `response_model` у *декоратора операции пути*, чтобы задавать модели ответа, и особенно — чтобы приватные данные отфильтровывались.
|
||||
|
||||
А также используйте `response_model_exclude_unset`, чтобы возвращать только те значения, которые были заданы явно.
|
||||
Используйте `response_model_exclude_unset`, чтобы возвращать только те значения, которые были установлены явно.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# HTTP коды статуса ответа
|
||||
# Статус-код ответа { #response-status-code }
|
||||
|
||||
Вы можете задать HTTP код статуса ответа с помощью параметра `status_code` подобно тому, как вы определяете схему ответа в любой из *операций пути*:
|
||||
Подобно тому, как вы можете задать модель/схему ответа, вы можете объявить HTTP статус-код, используемый для ответа, с помощью параметра `status_code` в любой из *операций пути*:
|
||||
|
||||
* `@app.get()`
|
||||
* `@app.post()`
|
||||
@@ -12,11 +12,11 @@
|
||||
|
||||
/// note | Примечание
|
||||
|
||||
Обратите внимание, что `status_code` является атрибутом метода-декоратора (`get`, `post` и т.д.), а не *функции-обработчика пути* в отличие от всех остальных параметров и тела запроса.
|
||||
Обратите внимание, что `status_code` — это параметр метода-декоратора (`get`, `post` и т.д.), а не вашей *функции-обработчика пути*, в отличие от всех остальных параметров и тела запроса.
|
||||
|
||||
///
|
||||
|
||||
Параметр `status_code` принимает число, обозначающее HTTP код статуса ответа.
|
||||
Параметр `status_code` принимает число, обозначающее HTTP статус-код.
|
||||
|
||||
/// info | Информация
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
Это позволит:
|
||||
|
||||
* Возвращать указанный код статуса в ответе.
|
||||
* Документировать его как код статуса ответа в OpenAPI схеме (а значит, и в пользовательском интерфейсе):
|
||||
* Документировать его как код статуса ответа в OpenAPI схеме (а значит, и в пользовательских интерфейсах):
|
||||
|
||||
<img src="/img/tutorial/response-status-code/image01.png">
|
||||
|
||||
@@ -39,11 +39,11 @@ FastAPI знает об этом и создаст документацию Open
|
||||
|
||||
///
|
||||
|
||||
## Об HTTP кодах статуса ответа
|
||||
## Об HTTP статус-кодах { #about-http-status-codes }
|
||||
|
||||
/// note | Примечание
|
||||
|
||||
Если вы уже знаете, что представляют собой HTTP коды статуса ответа, можете перейти к следующему разделу.
|
||||
Если вы уже знаете, что представляют собой HTTP статус-коды, можете перейти к следующему разделу.
|
||||
|
||||
///
|
||||
|
||||
@@ -51,26 +51,26 @@ FastAPI знает об этом и создаст документацию Open
|
||||
|
||||
У кодов статуса есть названия, чтобы упростить их распознавание, но важны именно числовые значения.
|
||||
|
||||
Кратко о значениях кодов:
|
||||
Кратко:
|
||||
|
||||
* `1XX` – статус-коды информационного типа. Они редко используются разработчиками напрямую. Ответы с этими кодами не могут иметь тела.
|
||||
* **`2XX`** – статус-коды, сообщающие об успешной обработке запроса. Они используются чаще всего.
|
||||
* `100 - 199` – статус-коды информационного типа. Они редко используются разработчиками напрямую. Ответы с этими кодами не могут иметь тела.
|
||||
* **`200 - 299`** – статус-коды, сообщающие об успешной обработке запроса. Они используются чаще всего.
|
||||
* `200` – это код статуса ответа по умолчанию, который означает, что все прошло "OK".
|
||||
* Другим примером может быть статус `201`, "Created". Он обычно используется после создания новой записи в базе данных.
|
||||
* Особый случай – `204`, "No Content". Этот статус ответа используется, когда нет содержимого для возврата клиенту, и поэтому ответ не должен иметь тела.
|
||||
* **`3XX`** – статус-коды, сообщающие о перенаправлениях. Ответы с этими кодами статуса могут иметь или не иметь тело, за исключением ответов со статусом `304`, "Not Modified", у которых не должно быть тела.
|
||||
* **`4XX`** – статус-коды, сообщающие о клиентской ошибке. Это ещё одна наиболее часто используемая категория.
|
||||
* Особый случай – `204`, "No Content". Этот статус ответа используется, когда нет содержимого для возврата клиенту, и поэтому ответ не должен иметь тела.
|
||||
* **`300 - 399`** – статус-коды, сообщающие о перенаправлениях. Ответы с этими кодами статуса могут иметь или не иметь тело, за исключением ответов со статусом `304`, "Not Modified", у которых не должно быть тела.
|
||||
* **`400 - 499`** – статус-коды, сообщающие о клиентской ошибке. Это ещё одна наиболее часто используемая категория.
|
||||
* Пример – код `404` для статуса "Not Found".
|
||||
* Для общих ошибок со стороны клиента можно просто использовать код `400`.
|
||||
* `5XX` – статус-коды, сообщающие о серверной ошибке. Они почти никогда не используются разработчиками напрямую. Когда что-то идет не так в какой-то части кода вашего приложения или на сервере, он автоматически вернёт один из 5XX кодов.
|
||||
* `500 - 599` – статус-коды, сообщающие о серверной ошибке. Они почти никогда не используются разработчиками напрямую. Когда что-то идет не так в какой-то части кода вашего приложения или на сервере, он автоматически вернёт один из этих кодов статуса.
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Чтобы узнать больше о HTTP кодах статуса и о том, для чего каждый из них предназначен, ознакомьтесь с <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status" class="external-link" target="_blank">документацией <abbr title="Mozilla Developer Network">MDN</abbr> об HTTP кодах статуса ответа</a>.
|
||||
Чтобы узнать больше о HTTP кодах статуса и о том, для чего каждый из них предназначен, ознакомьтесь с <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status" class="external-link" target="_blank"><abbr title="Mozilla Developer Network – Сеть разработчиков Mozilla">MDN</abbr> документацией об HTTP статус-кодах</a>.
|
||||
|
||||
///
|
||||
|
||||
## Краткие обозначения для запоминания названий кодов
|
||||
## Краткие обозначения для запоминания названий кодов { #shortcut-to-remember-the-names }
|
||||
|
||||
Рассмотрим предыдущий пример еще раз:
|
||||
|
||||
@@ -84,7 +84,7 @@ FastAPI знает об этом и создаст документацию Open
|
||||
|
||||
{* ../../docs_src/response_status_code/tutorial002.py hl[1,6] *}
|
||||
|
||||
Они содержат те же числовые значения, но позволяют использовать подсказки редактора для выбора кода статуса:
|
||||
Они содержат те же числовые значения, но позволяют использовать автозавершение редактора кода для выбора кода статуса:
|
||||
|
||||
<img src="/img/tutorial/response-status-code/image02.png">
|
||||
|
||||
@@ -96,6 +96,6 @@ FastAPI знает об этом и создаст документацию Open
|
||||
|
||||
///
|
||||
|
||||
## Изменение кода статуса по умолчанию
|
||||
## Изменение кода статуса по умолчанию { #changing-the-default }
|
||||
|
||||
Позже, в [Руководстве для продвинутых пользователей](../advanced/response-change-status-code.md){.internal-link target=_blank}, вы узнаете, как возвращать HTTP коды статуса, отличные от используемого здесь кода статуса по умолчанию.
|
||||
Позже, в [Руководстве для продвинутых пользователей](../advanced/response-change-status-code.md){.internal-link target=_blank}, вы узнаете, как возвращать HTTP статус-код, отличный от значения по умолчанию, которое вы объявляете здесь.
|
||||
|
||||
@@ -1,42 +1,70 @@
|
||||
# Объявление примера запроса данных
|
||||
# Объявление примеров данных запроса { #declare-request-example-data }
|
||||
|
||||
Вы можете объявлять примеры данных, которые ваше приложение может получать.
|
||||
|
||||
Вот несколько способов, как это можно сделать.
|
||||
Вот несколько способов, как это сделать.
|
||||
|
||||
## Pydantic `schema_extra`
|
||||
## Дополнительные данные JSON Schema в моделях Pydantic { #extra-json-schema-data-in-pydantic-models }
|
||||
|
||||
Вы можете объявить ключ `example` для модели Pydantic, используя класс `Config` и переменную `schema_extra`, как описано в <a href="https://docs.pydantic.dev/latest/concepts/json_schema/#schema-customization" class="external-link" target="_blank">Pydantic документации: Настройка схемы</a>:
|
||||
Вы можете объявить `examples` для модели Pydantic, которые будут добавлены в сгенерированную JSON Schema.
|
||||
|
||||
{* ../../docs_src/schema_extra_example/tutorial001_py310.py hl[13:21] *}
|
||||
//// tab | Pydantic v2
|
||||
|
||||
Эта дополнительная информация будет включена в **JSON Schema** выходных данных для этой модели, и она будет использоваться в документации к API.
|
||||
{* ../../docs_src/schema_extra_example/tutorial001_py310.py hl[13:24] *}
|
||||
|
||||
////
|
||||
|
||||
//// tab | Pydantic v1
|
||||
|
||||
{* ../../docs_src/schema_extra_example/tutorial001_pv1_py310.py hl[13:23] *}
|
||||
|
||||
////
|
||||
|
||||
Эта дополнительная информация будет добавлена как есть в выходную **JSON Schema** этой модели и будет использоваться в документации API.
|
||||
|
||||
//// tab | Pydantic v2
|
||||
|
||||
В Pydantic версии 2 вы будете использовать атрибут `model_config`, который принимает `dict`, как описано в <a href="https://docs.pydantic.dev/latest/api/config/" class="external-link" target="_blank">Документации Pydantic: Конфигурация</a>.
|
||||
|
||||
Вы можете задать `"json_schema_extra"` с `dict`, содержащим любые дополнительные данные, которые вы хотите видеть в сгенерированной JSON Schema, включая `examples`.
|
||||
|
||||
////
|
||||
|
||||
//// tab | Pydantic v1
|
||||
|
||||
В Pydantic версии 1 вы будете использовать внутренний класс `Config` и `schema_extra`, как описано в <a href="https://docs.pydantic.dev/1.10/usage/schema/#schema-customization" class="external-link" target="_blank">Документации Pydantic: Настройка схемы</a>.
|
||||
|
||||
Вы можете задать `schema_extra` со `dict`, содержащим любые дополнительные данные, которые вы хотите видеть в сгенерированной JSON Schema, включая `examples`.
|
||||
|
||||
////
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Вы можете использовать тот же метод для расширения JSON-схемы и добавления своей собственной дополнительной информации.
|
||||
Вы можете использовать тот же приём, чтобы расширить JSON Schema и добавить свою собственную дополнительную информацию.
|
||||
|
||||
Например, вы можете использовать это для добавления дополнительной информации для пользовательского интерфейса в вашем веб-приложении и т.д.
|
||||
Например, вы можете использовать это, чтобы добавить метаданные для фронтенд‑пользовательского интерфейса и т.д.
|
||||
|
||||
///
|
||||
|
||||
## Дополнительные аргументы поля `Field`
|
||||
/// info | Информация
|
||||
|
||||
При использовании `Field()` с моделями Pydantic, вы также можете объявлять дополнительную информацию для **JSON Schema**, передавая любые другие произвольные аргументы в функцию.
|
||||
OpenAPI 3.1.0 (используется начиная с FastAPI 0.99.0) добавил поддержку `examples`, который является частью стандарта **JSON Schema**.
|
||||
|
||||
Вы можете использовать это, чтобы добавить аргумент `example` для каждого поля:
|
||||
До этого поддерживалось только ключевое слово `example` с одним примером. Оно всё ещё поддерживается в OpenAPI 3.1.0, но помечено как устаревшее и не является частью стандарта JSON Schema. Поэтому рекомендуется мигрировать `example` на `examples`. 🤓
|
||||
|
||||
Подробнее — в конце этой страницы.
|
||||
|
||||
///
|
||||
|
||||
## Дополнительные аргументы `Field` { #field-additional-arguments }
|
||||
|
||||
При использовании `Field()` с моделями Pydantic вы также можете объявлять дополнительные `examples`:
|
||||
|
||||
{* ../../docs_src/schema_extra_example/tutorial002_py310.py hl[2,8:11] *}
|
||||
|
||||
/// warning | Внимание
|
||||
## `examples` в JSON Schema — OpenAPI { #examples-in-json-schema-openapi }
|
||||
|
||||
Имейте в виду, что эти дополнительные переданные аргументы не добавляют никакой валидации, только дополнительную информацию для документации.
|
||||
|
||||
///
|
||||
|
||||
## Использование `example` и `examples` в OpenAPI
|
||||
|
||||
При использовании любой из этих функций:
|
||||
При использовании любой из функций:
|
||||
|
||||
* `Path()`
|
||||
* `Query()`
|
||||
@@ -46,65 +74,151 @@
|
||||
* `Form()`
|
||||
* `File()`
|
||||
|
||||
вы также можете добавить аргумент, содержащий `example` или группу `examples` с дополнительной информацией, которая будет добавлена в **OpenAPI**.
|
||||
вы также можете объявить набор `examples` с дополнительной информацией, которая будет добавлена в их **JSON Schema** внутри **OpenAPI**.
|
||||
|
||||
### Параметр `Body` с аргументом `example`
|
||||
### `Body` с `examples` { #body-with-examples }
|
||||
|
||||
Здесь мы передаём аргумент `example`, как пример данных ожидаемых в параметре `Body()`:
|
||||
Здесь мы передаём `examples`, содержащий один пример данных, ожидаемых в `Body()`:
|
||||
|
||||
{* ../../docs_src/schema_extra_example/tutorial003_an_py310.py hl[22:27] *}
|
||||
{* ../../docs_src/schema_extra_example/tutorial003_an_py310.py hl[22:29] *}
|
||||
|
||||
### Аргумент "example" в UI документации
|
||||
### Пример в UI документации { #example-in-the-docs-ui }
|
||||
|
||||
С любым из вышеуказанных методов это будет выглядеть так в `/docs`:
|
||||
С любым из перечисленных выше методов это будет выглядеть так в `/docs`:
|
||||
|
||||
<img src="/img/tutorial/body-fields/image01.png">
|
||||
|
||||
### `Body` с аргументом `examples`
|
||||
### `Body` с несколькими `examples` { #body-with-multiple-examples }
|
||||
|
||||
В качестве альтернативы одному аргументу `example`, вы можете передавать `examples` используя тип данных `dict` с **несколькими примерами**, каждый из которых содержит дополнительную информацию, которая также будет добавлена в **OpenAPI**.
|
||||
Конечно, вы можете передать и несколько `examples`:
|
||||
|
||||
Ключи `dict` указывают на каждый пример, а значения для каждого из них - на еще один тип `dict` с дополнительной информацией.
|
||||
{* ../../docs_src/schema_extra_example/tutorial004_an_py310.py hl[23:38] *}
|
||||
|
||||
Каждый конкретный пример типа `dict` в аргументе `examples` может содержать:
|
||||
Когда вы делаете это, примеры становятся частью внутренней **JSON Schema** для данных тела запроса.
|
||||
|
||||
* `summary`: Краткое описание для примера.
|
||||
* `description`: Полное описание, которое может содержать текст в формате Markdown.
|
||||
* `value`: Это конкретный пример, который отображается, например, в виде типа `dict`.
|
||||
* `externalValue`: альтернатива параметру `value`, URL-адрес, указывающий на пример. Хотя это может не поддерживаться таким же количеством инструментов разработки и тестирования API, как параметр `value`.
|
||||
Тем не менее, на <abbr title="2023-08-26">момент написания этого</abbr> Swagger UI, инструмент, отвечающий за отображение UI документации, не поддерживает показ нескольких примеров для данных в **JSON Schema**. Но ниже есть обходной путь.
|
||||
|
||||
{* ../../docs_src/schema_extra_example/tutorial004_an_py310.py hl[23:49] *}
|
||||
### Специфические для OpenAPI `examples` { #openapi-specific-examples }
|
||||
|
||||
### Аргумент "examples" в UI документации
|
||||
Ещё до того как **JSON Schema** поддержала `examples`, в OpenAPI была поддержка другого поля, также называемого `examples`.
|
||||
|
||||
С аргументом `examples`, добавленным в `Body()`, страница документации `/docs` будет выглядеть так:
|
||||
Эти **специфические для OpenAPI** `examples` находятся в другой секции спецификации OpenAPI. Они находятся в **подробностях для каждой операции пути (обработчика пути)**, а не внутри каждого объекта Schema.
|
||||
|
||||
И Swagger UI уже какое‑то время поддерживает именно это поле `examples`. Поэтому вы можете использовать его, чтобы **отобразить** разные **примеры в UI документации**.
|
||||
|
||||
Структура этого специфичного для OpenAPI поля `examples` — это `dict` с **несколькими примерами** (вместо `list`), каждый с дополнительной информацией, которая также будет добавлена в **OpenAPI**.
|
||||
|
||||
Это не помещается внутрь каждого объекта Schema в OpenAPI, это находится снаружи, непосредственно на уровне самой *операции пути*.
|
||||
|
||||
### Использование параметра `openapi_examples` { #using-the-openapi-examples-parameter }
|
||||
|
||||
Вы можете объявлять специфические для OpenAPI `examples` в FastAPI с помощью параметра `openapi_examples` для:
|
||||
|
||||
* `Path()`
|
||||
* `Query()`
|
||||
* `Header()`
|
||||
* `Cookie()`
|
||||
* `Body()`
|
||||
* `Form()`
|
||||
* `File()`
|
||||
|
||||
Ключи `dict` идентифицируют каждый пример, а каждое значение — это ещё один `dict`.
|
||||
|
||||
Каждый конкретный пример‑`dict` в `examples` может содержать:
|
||||
|
||||
* `summary`: Краткое описание примера.
|
||||
* `description`: Подробное описание, которое может содержать текст в Markdown.
|
||||
* `value`: Это фактический пример, который отображается, например, `dict`.
|
||||
* `externalValue`: альтернатива `value`, URL, указывающий на пример. Хотя это может поддерживаться не так многими инструментами, как `value`.
|
||||
|
||||
Использовать это можно так:
|
||||
|
||||
{* ../../docs_src/schema_extra_example/tutorial005_an_py310.py hl[23:49] *}
|
||||
|
||||
### OpenAPI-примеры в UI документации { #openapi-examples-in-the-docs-ui }
|
||||
|
||||
С `openapi_examples`, добавленным в `Body()`, страница `/docs` будет выглядеть так:
|
||||
|
||||
<img src="/img/tutorial/body-fields/image02.png">
|
||||
|
||||
## Технические Детали
|
||||
## Технические детали { #technical-details }
|
||||
|
||||
/// warning | Внимание
|
||||
/// tip | Подсказка
|
||||
|
||||
Эти технические детали относятся к стандартам **JSON Schema** и **OpenAPI**.
|
||||
Если вы уже используете **FastAPI** версии **0.99.0 или выше**, вы, вероятно, можете **пропустить** эти подробности.
|
||||
|
||||
Если предложенные выше идеи уже работают для вас, возможно этого будет достаточно и эти детали вам не потребуются, можете спокойно их пропустить.
|
||||
Они более актуальны для старых версий, до того как стала доступна OpenAPI 3.1.0.
|
||||
|
||||
Считайте это кратким **уроком истории** про OpenAPI и JSON Schema. 🤓
|
||||
|
||||
///
|
||||
|
||||
Когда вы добавляете пример внутрь модели Pydantic, используя `schema_extra` или `Field(example="something")`, этот пример добавляется в **JSON Schema** для данной модели Pydantic.
|
||||
/// warning | Внимание
|
||||
|
||||
Далее идут очень технические подробности о стандартах **JSON Schema** и **OpenAPI**.
|
||||
|
||||
Если идеи выше уже работают для вас, этого может быть достаточно, и, вероятно, вам не нужны эти детали — смело пропускайте их.
|
||||
|
||||
///
|
||||
|
||||
До OpenAPI 3.1.0 OpenAPI использовала более старую и модифицированную версию **JSON Schema**.
|
||||
|
||||
В JSON Schema не было `examples`, поэтому OpenAPI добавила собственное поле `example` в свою модифицированную версию.
|
||||
|
||||
OpenAPI также добавила поля `example` и `examples` в другие части спецификации:
|
||||
|
||||
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#parameter-object" class="external-link" target="_blank">`Parameter Object` (в спецификации)</a>, которое использовалось в FastAPI:
|
||||
* `Path()`
|
||||
* `Query()`
|
||||
* `Header()`
|
||||
* `Cookie()`
|
||||
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#media-type-object" class="external-link" target="_blank">`Request Body Object`, в поле `content`, в `Media Type Object` (в спецификации)</a>, которое использовалось в FastAPI:
|
||||
* `Body()`
|
||||
* `File()`
|
||||
* `Form()`
|
||||
|
||||
/// info | Информация
|
||||
|
||||
Этот старый специфичный для OpenAPI параметр `examples` теперь называется `openapi_examples`, начиная с FastAPI `0.103.0`.
|
||||
|
||||
///
|
||||
|
||||
### Поле `examples` в JSON Schema { #json-schemas-examples-field }
|
||||
|
||||
Позже в новой версии спецификации JSON Schema было добавлено поле <a href="https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.9.5" class="external-link" target="_blank">`examples`</a>.
|
||||
|
||||
А затем новый OpenAPI 3.1.0 был основан на последней версии (JSON Schema 2020-12), которая включала это новое поле `examples`.
|
||||
|
||||
И теперь это новое поле `examples` имеет приоритет над старым одиночным (и кастомным) полем `example`, которое теперь устарело.
|
||||
|
||||
Это новое поле `examples` в JSON Schema — это **просто `list`** примеров, а не dict с дополнительными метаданными, как в других местах OpenAPI (описанных выше).
|
||||
|
||||
/// info | Информация
|
||||
|
||||
Даже после того как OpenAPI 3.1.0 была выпущена с этой новой, более простой интеграцией с JSON Schema, какое‑то время Swagger UI, инструмент, предоставляющий автоматическую документацию, не поддерживал OpenAPI 3.1.0 (поддержка появилась начиная с версии 5.0.0 🎉).
|
||||
|
||||
Из‑за этого версии FastAPI до 0.99.0 всё ещё использовали версии OpenAPI ниже 3.1.0.
|
||||
|
||||
///
|
||||
|
||||
### `examples` в Pydantic и FastAPI { #pydantic-and-fastapi-examples }
|
||||
|
||||
Когда вы добавляете `examples` внутри модели Pydantic, используя `schema_extra` или `Field(examples=["something"])`, этот пример добавляется в **JSON Schema** для этой модели Pydantic.
|
||||
|
||||
И эта **JSON Schema** модели Pydantic включается в **OpenAPI** вашего API, а затем используется в UI документации.
|
||||
|
||||
Поля `example` как такового не существует в стандартах **JSON Schema**. В последних версиях JSON-схемы определено поле <a href="https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.9.5" class="external-link" target="_blank">`examples`</a>, но OpenAPI 3.0.3 основан на более старой версии JSON-схемы, которая не имела поля `examples`.
|
||||
В версиях FastAPI до 0.99.0 (0.99.0 и выше используют новый OpenAPI 3.1.0), когда вы использовали `example` или `examples` с любыми другими утилитами (`Query()`, `Body()`, и т.д.), эти примеры не добавлялись в JSON Schema, описывающую эти данные (даже в собственную версию JSON Schema OpenAPI), они добавлялись непосредственно в объявление *операции пути* в OpenAPI (вне частей OpenAPI, использующих JSON Schema).
|
||||
|
||||
Таким образом, OpenAPI 3.0.3 определяет своё собственное поле <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#fixed-fields-20" class="external-link" target="_blank">`example`</a> для модифицированной версии **JSON Schema**, которую он использует чтобы достичь той же цели (однако это именно поле `example`, а не `examples`), и именно это используется API в UI документации (с интеграцией Swagger UI).
|
||||
Но теперь, когда FastAPI 0.99.0 и выше используют OpenAPI 3.1.0, который использует JSON Schema 2020-12, а также Swagger UI 5.0.0 и выше, всё стало более последовательным, и примеры включаются в JSON Schema.
|
||||
|
||||
Итак, хотя поле `example` не является частью JSON-схемы, оно является частью настраиваемой версии JSON-схемы в OpenAPI, и именно это поле будет использоваться в UI документации.
|
||||
### Swagger UI и специфичные для OpenAPI `examples` { #swagger-ui-and-openapi-specific-examples }
|
||||
|
||||
Однако, когда вы используете поле `example` или `examples` с любой другой функцией (`Query()`, `Body()`, и т.д.), эти примеры не добавляются в JSON-схему, которая описывает эти данные (даже в собственную версию JSON-схемы OpenAPI), они добавляются непосредственно в объявление *операции пути* в OpenAPI (вне частей OpenAPI, которые используют JSON-схему).
|
||||
Раньше, поскольку Swagger UI не поддерживал несколько примеров JSON Schema (по состоянию на 2023-08-26), у пользователей не было способа показать несколько примеров в документации.
|
||||
|
||||
Для функций `Path()`, `Query()`, `Header()`, и `Cookie()`, аргументы `example` или `examples` добавляются в <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#parameter-object" class="external-link" target="_blank">определение OpenAPI, к объекту `Parameter Object` (в спецификации)</a>.
|
||||
Чтобы решить это, FastAPI `0.103.0` **добавил поддержку** объявления того же старого, **специфичного для OpenAPI**, поля `examples` с новым параметром `openapi_examples`. 🤓
|
||||
|
||||
И для функций `Body()`, `File()` и `Form()` аргументы `example` или `examples` аналогично добавляются в <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#mediaTypeObject" class="external-link" target="_blank"> определение OpenAPI, к объекту `Request Body Object`, в поле `content` в объекте `Media Type Object` (в спецификации)</a>.
|
||||
### Итог { #summary }
|
||||
|
||||
С другой стороны, существует более новая версия OpenAPI: **3.1.0**, недавно выпущенная. Она основана на последней версии JSON-схемы и большинство модификаций из OpenAPI JSON-схемы удалены в обмен на новые возможности из последней версии JSON-схемы, так что все эти мелкие отличия устранены. Тем не менее, Swagger UI в настоящее время не поддерживает OpenAPI 3.1.0, поэтому пока лучше продолжать использовать вышеупомянутые методы.
|
||||
Раньше я говорил, что не очень люблю историю... а теперь вот рассказываю «уроки технической истории». 😅
|
||||
|
||||
Коротко: **обновитесь до FastAPI 0.99.0 или выше** — так всё будет значительно **проще, последовательнее и интуитивнее**, и вам не придётся знать все эти исторические подробности. 😎
|
||||
|
||||
@@ -1,52 +1,58 @@
|
||||
# Безопасность - первые шаги
|
||||
# Безопасность — первые шаги { #security-first-steps }
|
||||
|
||||
Представим, что у вас есть свой **бэкенд** API на некотором домене.
|
||||
Представим, что у вас есть **бэкенд** API на некотором домене.
|
||||
|
||||
И у вас есть **фронтенд** на другом домене или на другом пути того же домена (или в мобильном приложении).
|
||||
|
||||
И вы хотите иметь возможность аутентификации фронтенда с бэкендом, используя **имя пользователя** и **пароль**.
|
||||
И вы хотите, чтобы фронтенд мог аутентифицироваться на бэкенде, используя **имя пользователя** и **пароль**.
|
||||
|
||||
Мы можем использовать **OAuth2** для создания такой системы с помощью **FastAPI**.
|
||||
Мы можем использовать **OAuth2**, чтобы построить это с **FastAPI**.
|
||||
|
||||
Но давайте избавим вас от необходимости читать всю длинную спецификацию, чтобы найти те небольшие кусочки информации, которые вам нужны.
|
||||
Но давайте сэкономим вам время на чтение всей длинной спецификации в поисках тех небольших фрагментов информации, которые вам нужны.
|
||||
|
||||
Для работы с безопасностью воспользуемся средствами, предоставленными **FastAPI**.
|
||||
Воспользуемся инструментами, предоставленными **FastAPI**, чтобы работать с безопасностью.
|
||||
|
||||
## Как это выглядит
|
||||
## Как это выглядит { #how-it-looks }
|
||||
|
||||
Давайте сначала просто воспользуемся кодом и посмотрим, как он работает, а затем детально разберём, что происходит.
|
||||
Сначала просто воспользуемся кодом и посмотрим, как он работает, а затем вернемся и разберемся, что происходит.
|
||||
|
||||
## Создание `main.py`
|
||||
## Создание `main.py` { #create-main-py }
|
||||
|
||||
Скопируйте пример в файл `main.py`:
|
||||
|
||||
{* ../../docs_src/security/tutorial001_an_py39.py *}
|
||||
|
||||
## Запуск
|
||||
## Запуск { #run-it }
|
||||
|
||||
/// info | Дополнительная информация
|
||||
|
||||
Вначале, установите библиотеку <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>.
|
||||
Пакет <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a> автоматически устанавливается вместе с **FastAPI**, если вы запускаете команду `pip install "fastapi[standard]"`.
|
||||
|
||||
А именно: `pip install python-multipart`.
|
||||
Однако, если вы используете команду `pip install fastapi`, пакет `python-multipart` по умолчанию не включается.
|
||||
|
||||
Это связано с тем, что **OAuth2** использует "данные формы" для передачи `имени пользователя` и `пароля`.
|
||||
Чтобы установить его вручную, убедитесь, что вы создали [виртуальное окружение](../../virtual-environments.md){.internal-link target=_blank}, активировали его и затем установили пакет:
|
||||
|
||||
```console
|
||||
$ pip install python-multipart
|
||||
```
|
||||
|
||||
Это связано с тем, что **OAuth2** использует «данные формы» для отправки `username` и `password`.
|
||||
|
||||
///
|
||||
|
||||
Запустите ваш сервер:
|
||||
Запустите пример командой:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ uvicorn main:app --reload
|
||||
$ fastapi dev main.py
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## Проверка
|
||||
## Проверка { #check-it }
|
||||
|
||||
Перейдите к интерактивной документации по адресу: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
|
||||
|
||||
@@ -56,73 +62,75 @@ $ uvicorn main:app --reload
|
||||
|
||||
/// check | Кнопка авторизации!
|
||||
|
||||
У вас уже появилась новая кнопка "Authorize".
|
||||
У вас уже появилась новая кнопка «Authorize».
|
||||
|
||||
А у *операции пути* теперь появился маленький замочек в правом верхнем углу, на который можно нажать.
|
||||
А у вашей *операции пути* в правом верхнем углу есть маленький замочек, на который можно нажать.
|
||||
|
||||
///
|
||||
|
||||
При нажатии на нее появляется небольшая форма авторизации, в которую нужно ввести `имя пользователя` и `пароль` (и другие необязательные поля):
|
||||
Если нажать на нее, появится небольшая форма авторизации, в которую нужно ввести `username` и `password` (и другие необязательные поля):
|
||||
|
||||
<img src="/img/tutorial/security/image02.png">
|
||||
|
||||
/// note | Технические детали
|
||||
/// note | Примечание
|
||||
|
||||
Неважно, что вы введете в форму, она пока не будет работать. Но мы к этому еще придем.
|
||||
Неважно, что вы введете в форму — пока это не будет работать. Но мы скоро до этого дойдем.
|
||||
|
||||
///
|
||||
|
||||
Конечно, это не фронтенд для конечных пользователей, но это отличный автоматический инструмент для интерактивного документирования всех ваших API.
|
||||
Конечно, это не фронтенд для конечных пользователей, но это отличный автоматический инструмент для интерактивного документирования всего вашего API.
|
||||
|
||||
Он может использоваться командой фронтенда (которой можете быть и вы сами).
|
||||
Им может пользоваться команда фронтенда (которой можете быть и вы сами).
|
||||
|
||||
Он может быть использован сторонними приложениями и системами.
|
||||
Им могут пользоваться сторонние приложения и системы.
|
||||
|
||||
Кроме того, его можно использовать самостоятельно для отладки, проверки и тестирования одного и того же приложения.
|
||||
И им также можете пользоваться вы сами, чтобы отлаживать, проверять и тестировать то же самое приложение.
|
||||
|
||||
## Аутентификация по паролю
|
||||
## «`password` flow» (аутентификация по паролю) { #the-password-flow }
|
||||
|
||||
Теперь давайте вернемся немного назад и разберемся, что же это такое.
|
||||
Теперь давайте немного вернемся и разберемся, что это все такое.
|
||||
|
||||
Аутентификация по паролю является одним из способов, определенных в OAuth2, для обеспечения безопасности и аутентификации.
|
||||
«`password` flow» — это один из способов («flows»), определенных в OAuth2, для обеспечения безопасности и аутентификации.
|
||||
|
||||
OAuth2 был разработан для того, чтобы бэкэнд или API были независимы от сервера, который аутентифицирует пользователя.
|
||||
OAuth2 был спроектирован так, чтобы бэкенд или API были независимы от сервера, который аутентифицирует пользователя.
|
||||
|
||||
Но в нашем случае одно и то же приложение **FastAPI** будет работать с API и аутентификацией.
|
||||
Но в нашем случае одно и то же приложение **FastAPI** будет работать и с API, и с аутентификацией.
|
||||
|
||||
Итак, рассмотрим его с этой упрощенной точки зрения:
|
||||
Итак, рассмотрим это с упрощенной точки зрения:
|
||||
|
||||
* Пользователь вводит на фронтенде `имя пользователя` и `пароль` и нажимает `Enter`.
|
||||
* Фронтенд (работающий в браузере пользователя) отправляет эти `имя пользователя` и `пароль` на определенный URL в нашем API (объявленный с помощью параметра `tokenUrl="token"`).
|
||||
* API проверяет эти `имя пользователя` и `пароль` и выдает в ответ "токен" (мы еще не реализовали ничего из этого).
|
||||
* "Токен" - это просто строка с некоторым содержимым, которое мы можем использовать позже для верификации пользователя.
|
||||
* Обычно срок действия токена истекает через некоторое время.
|
||||
* Таким образом, пользователю придется снова войти в систему в какой-то момент времени.
|
||||
* И если токен будет украден, то риск будет меньше, так как он не похож на постоянный ключ, который будет работать вечно (в большинстве случаев).
|
||||
* Фронтенд временно хранит этот токен в каком-то месте.
|
||||
* Пользователь щелкает мышью на фронтенде, чтобы перейти в другой раздел на фронтенде.
|
||||
* Фронтенду необходимо получить дополнительные данные из API.
|
||||
* Но для этого необходима аутентификация для конкретной конечной точки.
|
||||
* Поэтому для аутентификации в нашем API он посылает заголовок `Authorization` со значением `Bearer` плюс сам токен.
|
||||
* Если токен содержит `foobar`, то содержание заголовка `Authorization` будет таким: `Bearer foobar`.
|
||||
* Пользователь вводит на фронтенде `username` и `password` и нажимает `Enter`.
|
||||
* Фронтенд (работающий в браузере пользователя) отправляет эти `username` и `password` на конкретный URL в нашем API (объявленный с `tokenUrl="token"`).
|
||||
* API проверяет этот `username` и `password` и отвечает «токеном» (мы еще ничего из этого не реализовали).
|
||||
* «Токен» — это просто строка с некоторым содержимым, которое мы сможем позже использовать для проверки этого пользователя.
|
||||
* Обычно у токена установлен срок действия: он истекает через некоторое время.
|
||||
* Поэтому пользователю придется снова войти в систему в какой‑то момент.
|
||||
* И если токен украдут, риск меньше: это не постоянный ключ, который будет работать вечно (в большинстве случаев).
|
||||
* Фронтенд временно где‑то хранит этот токен.
|
||||
* Пользователь кликает во фронтенде, чтобы перейти в другой раздел веб‑приложения.
|
||||
* Фронтенду нужно получить дополнительные данные из API.
|
||||
* Но для этого для конкретной конечной точки нужна аутентификация.
|
||||
* Поэтому, чтобы аутентифицироваться в нашем API, он отправляет HTTP-заголовок `Authorization` со значением `Bearer ` плюс сам токен.
|
||||
* Если токен содержит `foobar`, то содержимое заголовка `Authorization` будет: `Bearer foobar`.
|
||||
|
||||
## Класс `OAuth2PasswordBearer` в **FastAPI**
|
||||
## Класс `OAuth2PasswordBearer` в **FastAPI** { #fastapis-oauth2passwordbearer }
|
||||
|
||||
**FastAPI** предоставляет несколько средств на разных уровнях абстракции для реализации этих функций безопасности.
|
||||
|
||||
В данном примере мы будем использовать **OAuth2**, с аутентификацией по паролю, используя токен **Bearer**. Для этого мы используем класс `OAuth2PasswordBearer`.
|
||||
В этом примере мы будем использовать **OAuth2**, с потоком **Password**, используя токен **Bearer**. Для этого мы используем класс `OAuth2PasswordBearer`.
|
||||
|
||||
/// info | Дополнительная информация
|
||||
|
||||
Токен "bearer" - не единственный вариант, но для нашего случая он является наилучшим.
|
||||
Токен «bearer» — не единственный вариант.
|
||||
|
||||
И это может быть лучшим вариантом для большинства случаев использования, если только вы не являетесь экспертом в области OAuth2 и точно знаете, почему вам лучше подходит какой-то другой вариант.
|
||||
Но для нашего случая он — лучший.
|
||||
|
||||
В этом случае **FastAPI** также предоставляет инструменты для его реализации.
|
||||
И он может быть лучшим для большинства случаев использования, если только вы не являетесь экспертом по OAuth2 и точно знаете, почему другой вариант лучше подходит под ваши нужды.
|
||||
|
||||
В этом случае **FastAPI** также предоставляет инструменты, чтобы его реализовать.
|
||||
|
||||
///
|
||||
|
||||
При создании экземпляра класса `OAuth2PasswordBearer` мы передаем в него параметр `tokenUrl`. Этот параметр содержит URL, который клиент (фронтенд, работающий в браузере пользователя) будет использовать для отправки `имени пользователя` и `пароля` с целью получения токена.
|
||||
При создании экземпляра класса `OAuth2PasswordBearer` мы передаем параметр `tokenUrl`. Этот параметр содержит URL, который клиент (фронтенд, работающий в браузере пользователя) будет использовать для отправки `username` и `password`, чтобы получить токен.
|
||||
|
||||
{* ../../docs_src/security/tutorial001_an_py39.py hl[8] *}
|
||||
|
||||
@@ -130,27 +138,27 @@ OAuth2 был разработан для того, чтобы бэкэнд ил
|
||||
|
||||
Здесь `tokenUrl="token"` ссылается на относительный URL `token`, который мы еще не создали. Поскольку это относительный URL, он эквивалентен `./token`.
|
||||
|
||||
Поскольку мы используем относительный URL, если ваш API расположен по адресу `https://example.com/`, то он будет ссылаться на `https://example.com/token`. Если же ваш API расположен по адресу `https://example.com/api/v1/`, то он будет ссылаться на `https://example.com/api/v1/token`.
|
||||
Поскольку мы используем относительный URL, если ваш API расположен по адресу `https://example.com/`, то он будет ссылаться на `https://example.com/token`. А если ваш API расположен по адресу `https://example.com/api/v1/`, то он будет ссылаться на `https://example.com/api/v1/token`.
|
||||
|
||||
Использование относительного URL важно для того, чтобы ваше приложение продолжало работать даже в таких сложных случаях, как оно находится [за прокси-сервером](../../advanced/behind-a-proxy.md){.internal-link target=_blank}.
|
||||
Использование относительного URL важно для того, чтобы ваше приложение продолжало работать даже в таком продвинутом случае, как [За прокси-сервером](../../advanced/behind-a-proxy.md){.internal-link target=_blank}.
|
||||
|
||||
///
|
||||
|
||||
Этот параметр не создает конечную точку / *операцию пути*, а объявляет, что URL `/token` будет таким, который клиент должен использовать для получения токена. Эта информация используется в OpenAPI, а затем в интерактивных системах документации API.
|
||||
Этот параметр не создает конечную точку / *операцию пути*, а объявляет, что URL `/token` — это тот, который клиент должен использовать для получения токена. Эта информация используется в OpenAPI, а затем в интерактивных системах документации по API.
|
||||
|
||||
Вскоре мы создадим и саму операцию пути.
|
||||
Скоро мы также создадим и саму операцию пути.
|
||||
|
||||
/// info | Дополнительная информация
|
||||
|
||||
Если вы очень строгий "питонист", то вам может не понравиться стиль названия параметра `tokenUrl` вместо `token_url`.
|
||||
Если вы очень строгий «питонист», вам может не понравиться стиль имени параметра `tokenUrl` вместо `token_url`.
|
||||
|
||||
Это связано с тем, что тут используется то же имя, что и в спецификации OpenAPI. Таким образом, если вам необходимо более подробно изучить какую-либо из этих схем безопасности, вы можете просто использовать копирование/вставку, чтобы найти дополнительную информацию о ней.
|
||||
Это потому, что используется то же имя, что и в спецификации OpenAPI. Так, если вам нужно разобраться подробнее в какой‑то из этих схем безопасности, вы можете просто скопировать и вставить это имя, чтобы найти больше информации.
|
||||
|
||||
///
|
||||
|
||||
Переменная `oauth2_scheme` является экземпляром `OAuth2PasswordBearer`, но она также является "вызываемой".
|
||||
Переменная `oauth2_scheme` — это экземпляр `OAuth2PasswordBearer`, но она также «вызываемая».
|
||||
|
||||
Ее можно вызвать следующим образом:
|
||||
Ее можно вызвать так:
|
||||
|
||||
```Python
|
||||
oauth2_scheme(some, parameters)
|
||||
@@ -158,38 +166,38 @@ oauth2_scheme(some, parameters)
|
||||
|
||||
Поэтому ее можно использовать вместе с `Depends`.
|
||||
|
||||
### Использование
|
||||
### Использование { #use-it }
|
||||
|
||||
Теперь вы можете передать ваш `oauth2_scheme` в зависимость с помощью `Depends`.
|
||||
Теперь вы можете передать `oauth2_scheme` как зависимость с `Depends`.
|
||||
|
||||
{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *}
|
||||
|
||||
Эта зависимость будет предоставлять `строку`, которая присваивается параметру `token` в *функции операции пути*.
|
||||
Эта зависимость предоставит `str`, который будет присвоен параметру `token` *функции-обработчика пути*.
|
||||
|
||||
**FastAPI** будет знать, что он может использовать эту зависимость для определения "схемы безопасности" в схеме OpenAPI (и автоматической документации по API).
|
||||
**FastAPI** будет знать, что может использовать эту зависимость для определения «схемы безопасности» в схеме OpenAPI (и в автоматической документации по API).
|
||||
|
||||
/// info | Технические детали
|
||||
|
||||
**FastAPI** будет знать, что он может использовать класс `OAuth2PasswordBearer` (объявленный в зависимости) для определения схемы безопасности в OpenAPI, поскольку он наследуется от `fastapi.security.oauth2.OAuth2`, который, в свою очередь, наследуется от `fastapi.security.base.SecurityBase`.
|
||||
**FastAPI** будет знать, что может использовать класс `OAuth2PasswordBearer` (объявленный в зависимости) для определения схемы безопасности в OpenAPI, потому что он наследуется от `fastapi.security.oauth2.OAuth2`, который, в свою очередь, наследуется от `fastapi.security.base.SecurityBase`.
|
||||
|
||||
Все утилиты безопасности, интегрируемые в OpenAPI (и автоматическая документация по API), наследуются от `SecurityBase`, поэтому **FastAPI** может знать, как интегрировать их в OpenAPI.
|
||||
Все утилиты безопасности, интегрируемые с OpenAPI (и автоматической документацией по API), наследуются от `SecurityBase`, — так **FastAPI** понимает, как интегрировать их в OpenAPI.
|
||||
|
||||
///
|
||||
|
||||
## Что он делает
|
||||
## Что он делает { #what-it-does }
|
||||
|
||||
Он будет искать в запросе заголовок `Authorization` и проверять, содержит ли он значение `Bearer` с некоторым токеном, и возвращать токен в виде `строки`.
|
||||
Он будет искать в запросе заголовок `Authorization`, проверять, что его значение — это `Bearer ` плюс некоторый токен, и вернет токен как `str`.
|
||||
|
||||
Если он не видит заголовка `Authorization` или значение не имеет токена `Bearer`, то в ответ будет выдана ошибка с кодом состояния 401 (`UNAUTHORIZED`).
|
||||
Если заголовок `Authorization` отсутствует или его значение не содержит токен `Bearer `, он сразу ответит ошибкой со статус-кодом 401 (`UNAUTHORIZED`).
|
||||
|
||||
Для возврата ошибки даже не нужно проверять, существует ли токен. Вы можете быть уверены, что если ваша функция была выполнена, то в этом токене есть `строка`.
|
||||
Вам даже не нужно проверять наличие токена, чтобы вернуть ошибку. Вы можете быть уверены: если ваша функция была выполнена, в этом токене будет `str`.
|
||||
|
||||
Проверить это можно уже сейчас в интерактивной документации:
|
||||
Это уже можно попробовать в интерактивной документации:
|
||||
|
||||
<img src="/img/tutorial/security/image03.png">
|
||||
|
||||
Мы пока не проверяем валидность токена, но для начала неплохо.
|
||||
Мы пока не проверяем валидность токена, но для начала это уже неплохо.
|
||||
|
||||
## Резюме
|
||||
## Резюме { #recap }
|
||||
|
||||
Таким образом, всего за 3-4 дополнительные строки вы получаете некую примитивную форму защиты.
|
||||
Таким образом, всего за 3–4 дополнительные строки у вас уже есть некая примитивная форма защиты.
|
||||
|
||||
@@ -1,99 +1,105 @@
|
||||
# Данные текущего пользователя
|
||||
# Получить текущего пользователя { #get-current-user }
|
||||
|
||||
В предыдущей главе система безопасности (основанная на системе внедрения зависимостей) передавала *функции, обрабатывающей эндпоинт,* `токен` в виде `строки`:
|
||||
В предыдущей главе система безопасности (основанная на системе внедрения зависимостей) передавала *функции-обработчику пути* `token` типа `str`:
|
||||
|
||||
{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *}
|
||||
|
||||
Это пока что не слишком нам полезно. Давайте изменим код так, чтобы он возвращал нам данные пользователя, отправившего запрос.
|
||||
Но это всё ещё не слишком полезно.
|
||||
|
||||
## Создание модели пользователя
|
||||
Сделаем так, чтобы она возвращала текущего пользователя.
|
||||
|
||||
## Создать модель пользователя { #create-a-user-model }
|
||||
|
||||
Сначала создадим Pydantic-модель пользователя.
|
||||
|
||||
Точно так же, как мы использовали Pydantic для объявления тел запросов, мы можем использовать его где угодно:
|
||||
Точно так же, как мы используем Pydantic для объявления тел запросов, мы можем использовать его где угодно:
|
||||
|
||||
{* ../../docs_src/security/tutorial002_an_py310.py hl[5,12:6] *}
|
||||
|
||||
## Создание зависимости `get_current_user`
|
||||
## Создать зависимость `get_current_user` { #create-a-get-current-user-dependency }
|
||||
|
||||
Давайте создадим зависимость `get_current_user`.
|
||||
|
||||
Помните, что у зависимостей могут быть подзависимости?
|
||||
|
||||
`get_current_user` как раз будет иметь подзависимость `oauth2_scheme`, которую мы создали ранее.
|
||||
`get_current_user` будет иметь зависимость от того же `oauth2_scheme`, который мы создали ранее.
|
||||
|
||||
Аналогично тому, как мы делали это ранее в *обработчике эндпоинта* наша новая зависимость `get_current_user` будет получать `token` в виде `строки` от подзависимости `oauth2_scheme`:
|
||||
Аналогично тому, как мы делали ранее прямо в *операции пути*, новая зависимость `get_current_user` получит `token` типа `str` от подзависимости `oauth2_scheme`:
|
||||
|
||||
{* ../../docs_src/security/tutorial002_an_py310.py hl[25] *}
|
||||
|
||||
## Получение данных пользователя
|
||||
## Получить пользователя { #get-the-user }
|
||||
|
||||
`get_current_user` будет использовать созданную нами (ненастоящую) служебную функцию, которая принимает токен в виде `строки` и возвращает нашу Pydantic-модель `User`:
|
||||
`get_current_user` будет использовать созданную нами (ненастоящую) служебную функцию, которая принимает токен типа `str` и возвращает нашу Pydantic-модель `User`:
|
||||
|
||||
{* ../../docs_src/security/tutorial002_an_py310.py hl[19:22,26:27] *}
|
||||
|
||||
## Внедрение зависимости текущего пользователя
|
||||
## Внедрить текущего пользователя { #inject-the-current-user }
|
||||
|
||||
Теперь мы можем использовать тот же `Depends` с нашей зависимостью `get_current_user` в *функции обрабатывающей эндпоинт*:
|
||||
Теперь мы можем использовать тот же `Depends` с нашей `get_current_user` в *операции пути*:
|
||||
|
||||
{* ../../docs_src/security/tutorial002_an_py310.py hl[31] *}
|
||||
|
||||
Обратите внимание, что мы объявляем тип переменной `current_user` как Pydantic-модель `User`.
|
||||
Обратите внимание, что мы объявляем тип `current_user` как Pydantic-модель `User`.
|
||||
|
||||
Это поможет выполнить внутри функции все проверки автозаполнения и типа.
|
||||
Это поможет внутри функции с автозавершением и проверками типов.
|
||||
|
||||
/// tip | Подсказка
|
||||
Возможно, вы помните, что тело запроса также объявляется с помощью Pydantic-моделей.
|
||||
|
||||
В этом месте у **FastAPI** не возникнет проблем, потому что вы используете `Depends`.
|
||||
Возможно, вы помните, что тела запросов также объявляются с помощью Pydantic-моделей.
|
||||
|
||||
Здесь **FastAPI** не запутается, потому что вы используете `Depends`.
|
||||
|
||||
///
|
||||
|
||||
/// check | Заметка
|
||||
То, как устроена эта система зависимостей, позволяет нам иметь различные зависимости, которые возвращают модель `User`.
|
||||
|
||||
Мы не ограничены наличием только одной зависимости, которая может возвращать данные такого типа.
|
||||
То, как устроена эта система зависимостей, позволяет иметь разные зависимости, которые возвращают модель `User`.
|
||||
|
||||
Мы не ограничены наличием только одной зависимости, которая может возвращать такой тип данных.
|
||||
|
||||
///
|
||||
|
||||
## Другие модели
|
||||
## Другие модели { #other-models }
|
||||
|
||||
Теперь вы можете получать информацию о текущем пользователе непосредственно в *функции обрабатывающей эндпоинт* и работать с механизмами безопасности на уровне **Внедрения Зависимостей**, используя `Depends`.
|
||||
Теперь вы можете получать текущего пользователя напрямую в *функциях-обработчиках пути* и работать с механизмами безопасности на уровне **внедрения зависимостей**, используя `Depends`.
|
||||
|
||||
Причем для обеспечения требований безопасности можно использовать любую модель или данные (в данном случае - Pydantic-модель `User`).
|
||||
И вы можете использовать любую модель или данные для требований безопасности (в данном случае Pydantic-модель `User`).
|
||||
|
||||
Но вы не ограничены использованием какой-то конкретной моделью данных, классом или типом.
|
||||
Но вы не ограничены использованием какой-то конкретной модели данных, класса или типа.
|
||||
|
||||
Вы хотите использовать в модели `id` и `email`, а `username` вам не нужен? Ну разумеется. Воспользуйтесь тем же инструментарием.
|
||||
Хотите, чтобы в модели были `id` и `email`, но не было `username`? Пожалуйста. Можно использовать те же инструменты.
|
||||
|
||||
Вам нужны только `строки`? Или только `словари`? Или непосредственно экземпляр модели класса базы данных? Все это работает точно также.
|
||||
Хотите просто `str`? Или просто `dict`? Или напрямую экземпляр класса модели базы данных? Всё работает одинаково.
|
||||
|
||||
У вас нет пользователей, которые входят в ваше приложение, а только роботы, боты или другие системы, у которых есть только токен доступа? Опять же, все работает одинаково.
|
||||
У вас вообще нет пользователей, которые входят в приложение, а есть роботы, боты или другие системы, у которых есть только токен доступа? Снова — всё работает так же.
|
||||
|
||||
Просто используйте любую модель, любой класс, любую базу данных, которые нужны для вашего приложения. Система внедрения зависимостей **FastAPI** поможет вам в этом.
|
||||
Просто используйте любую модель, любой класс, любую базу данных, которые нужны вашему приложению. Система внедрения зависимостей **FastAPI** поможет вам в этом.
|
||||
|
||||
## Размер кода
|
||||
## Размер кода { #code-size }
|
||||
|
||||
Этот пример может показаться многословным. Следует иметь в виду, что в одном файле мы смешиваем безопасность, модели данных, служебные функции и *эндпоинты*.
|
||||
Этот пример может показаться многословным. Имейте в виду, что в одном файле мы смешиваем безопасность, модели данных, служебные функции и *операции пути*.
|
||||
|
||||
Но вот ключевой момент:
|
||||
Но вот ключевой момент.
|
||||
|
||||
Все, что касается безопасности и внедрения зависимостей, пишется один раз.
|
||||
Всё, что касается безопасности и внедрения зависимостей, пишется один раз.
|
||||
|
||||
И вы можете сделать его настолько сложным, насколько захотите. И все это будет написано только один раз, в одном месте, со всей своей гибкостью.
|
||||
И вы можете сделать это настолько сложным, насколько захотите. И всё равно это будет написано только один раз, в одном месте. Со всей гибкостью.
|
||||
|
||||
И у вас могут быть тысячи конечных точек (*эндпоинтов*), использующих одну и ту же систему безопасности.
|
||||
При этом у вас могут быть тысячи эндпоинтов (*операций пути*), использующих одну и ту же систему безопасности.
|
||||
|
||||
И все они (или любая их часть по вашему желанию) могут воспользоваться преимуществами повторного использования этих зависимостей или любых других зависимостей, которые вы создадите.
|
||||
|
||||
И все эти тысячи *эндпоинтов* могут составлять всего 3 строки:
|
||||
И все эти тысячи *операций пути* могут состоять всего из 3 строк:
|
||||
|
||||
{* ../../docs_src/security/tutorial002_an_py310.py hl[30:32] *}
|
||||
|
||||
## Резюме
|
||||
## Резюме { #recap }
|
||||
|
||||
Теперь вы можете получать данные о текущем пользователе непосредственно в своей *функции обработчике эндпоинта*.
|
||||
Теперь вы можете получать текущего пользователя прямо в своей *функции-обработчике пути*.
|
||||
|
||||
Мы уже на полпути к этому.
|
||||
Мы уже на полпути.
|
||||
|
||||
Осталось лишь добавить *эндпоинт* для отправки пользователем/клиентом своих `имени пользователя` и `пароля`.
|
||||
Нужно лишь добавить *операцию пути*, чтобы пользователь/клиент мог отправить `username` и `password`.
|
||||
|
||||
Это будет рассмотрено в следующем разделе.
|
||||
Это будет дальше.
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# OAuth2 с паролем (и хешированием), Bearer с JWT-токенами
|
||||
# OAuth2 с паролем (и хешированием), Bearer с JWT-токенами { #oauth2-with-password-and-hashing-bearer-with-jwt-tokens }
|
||||
|
||||
Теперь, когда у нас определен процесс обеспечения безопасности, давайте сделаем приложение действительно безопасным, используя токены <abbr title="JSON Web Tokens">JWT</abbr> и безопасное хеширование паролей.
|
||||
Теперь, когда у нас определен процесс обеспечения безопасности, давайте сделаем приложение действительно безопасным, используя токены <abbr title="JSON Web Tokens – веб‑токены JSON">JWT</abbr> и безопасное хеширование паролей.
|
||||
|
||||
Этот код можно реально использовать в своем приложении, сохранять хэши паролей в базе данных и т.д.
|
||||
|
||||
Мы продолжим разбираться, начиная с того места, на котором остановились в предыдущей главе.
|
||||
|
||||
## Про JWT
|
||||
## Про JWT { #about-jwt }
|
||||
|
||||
JWT означает "JSON Web Tokens".
|
||||
|
||||
@@ -26,7 +26,7 @@ eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4
|
||||
|
||||
Если вы хотите поиграть с JWT-токенами и посмотреть, как они работают, посмотрите <a href="https://jwt.io/" class="external-link" target="_blank">https://jwt.io</a>.
|
||||
|
||||
## Установка `PyJWT`
|
||||
## Установка `PyJWT` { #install-pyjwt }
|
||||
|
||||
Нам необходимо установить `pyjwt` для генерации и проверки JWT-токенов на языке Python.
|
||||
|
||||
@@ -45,10 +45,10 @@ $ pip install pyjwt
|
||||
/// info | Дополнительная информация
|
||||
Если вы планируете использовать алгоритмы цифровой подписи, такие как RSA или ECDSA, вам следует установить зависимость библиотеки криптографии `pyjwt[crypto]`.
|
||||
|
||||
Подробнее об этом можно прочитать в <a href=«https://pyjwt.readthedocs.io/en/latest/installation.html» class=«external-link» target=«_blank»>документации по установке PyJWT</a>.
|
||||
Подробнее об этом можно прочитать в <a href="https://pyjwt.readthedocs.io/en/latest/installation.html" class="external-link" target="_blank">документации по установке PyJWT</a>.
|
||||
///
|
||||
|
||||
## Хеширование паролей
|
||||
## Хеширование паролей { #password-hashing }
|
||||
|
||||
"Хеширование" означает преобразование некоторого содержимого (в данном случае пароля) в последовательность байтов (просто строку), которая выглядит как тарабарщина.
|
||||
|
||||
@@ -56,26 +56,26 @@ $ pip install pyjwt
|
||||
|
||||
Но преобразовать тарабарщину обратно в пароль невозможно.
|
||||
|
||||
### Для чего нужно хеширование паролей
|
||||
### Для чего нужно хеширование паролей { #why-use-password-hashing }
|
||||
|
||||
Если ваша база данных будет украдена, то вор не получит пароли пользователей в открытом виде, а только их хэши.
|
||||
|
||||
Таким образом, вор не сможет использовать этот пароль в другой системе (поскольку многие пользователи везде используют один и тот же пароль, это было бы опасно).
|
||||
|
||||
## Установка `passlib`
|
||||
## Установка `pwdlib` { #install-pwdlib }
|
||||
|
||||
PassLib - это отличный пакет Python для работы с хэшами паролей.
|
||||
pwdlib — это отличный пакет Python для работы с хэшами паролей.
|
||||
|
||||
Он поддерживает множество безопасных алгоритмов хеширования и утилит для работы с ними.
|
||||
|
||||
Рекомендуемый алгоритм - "Bcrypt".
|
||||
Рекомендуемый алгоритм — "Argon2".
|
||||
|
||||
Убедитесь, что вы создали и активировали виртуальное окружение, и затем установите PassLib вместе с Bcrypt:
|
||||
Убедитесь, что вы создали [виртуальное окружение](../../virtual-environments.md){.internal-link target=_blank}, активируйте его, и затем установите pwdlib вместе с Argon2:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install "passlib[bcrypt]"
|
||||
$ pip install "pwdlib[argon2]"
|
||||
|
||||
---> 100%
|
||||
```
|
||||
@@ -83,40 +83,40 @@ $ pip install "passlib[bcrypt]"
|
||||
</div>
|
||||
|
||||
/// tip | Подсказка
|
||||
С помощью `passlib` можно даже настроить его на чтение паролей, созданных **Django**, плагином безопасности **Flask** или многими другими библиотеками.
|
||||
С помощью `pwdlib` можно даже настроить его на чтение паролей, созданных **Django**, плагином безопасности **Flask** или многими другими библиотеками.
|
||||
|
||||
Таким образом, вы сможете, например, совместно использовать одни и те же данные из приложения Django в базе данных с приложением FastAPI. Или постепенно мигрировать Django-приложение, используя ту же базу данных.
|
||||
|
||||
При этом пользователи смогут одновременно входить в систему как из приложения Django, так и из приложения **FastAPI**.
|
||||
///
|
||||
|
||||
## Хеширование и проверка паролей
|
||||
## Хеширование и проверка паролей { #hash-and-verify-the-passwords }
|
||||
|
||||
Импортируйте необходимые инструменты из `passlib`.
|
||||
Импортируйте необходимые инструменты из `pwdlib`.
|
||||
|
||||
Создайте "контекст" PassLib. Именно он будет использоваться для хэширования и проверки паролей.
|
||||
Создайте экземпляр PasswordHash с рекомендованными настройками — он будет использоваться для хэширования и проверки паролей.
|
||||
|
||||
/// tip | Подсказка
|
||||
Контекст PassLib также имеет функциональность для использования различных алгоритмов хеширования, в том числе и устаревших, только для возможности их проверки и т.д.
|
||||
pwdlib также поддерживает алгоритм хеширования bcrypt, но не включает устаревшие алгоритмы — для работы с устаревшими хэшами рекомендуется использовать библиотеку passlib.
|
||||
|
||||
Например, вы можете использовать его для чтения и проверки паролей, сгенерированных другой системой (например, Django), но хэшировать все новые пароли другим алгоритмом, например Bcrypt.
|
||||
Например, вы можете использовать ее для чтения и проверки паролей, сгенерированных другой системой (например, Django), но хэшировать все новые пароли другим алгоритмом, например Argon2 или Bcrypt.
|
||||
|
||||
И при этом быть совместимым со всеми этими системами.
|
||||
///
|
||||
|
||||
Создайте служебную функцию для хэширования пароля, поступающего от пользователя.
|
||||
|
||||
А затем создайте другую - для проверки соответствия полученного пароля и хранимого хэша.
|
||||
А затем создайте другую — для проверки соответствия полученного пароля и хранимого хэша.
|
||||
|
||||
И еще одну - для аутентификации и возврата пользователя.
|
||||
И еще одну — для аутентификации и возврата пользователя.
|
||||
|
||||
{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,56:57,60:61,70:76] *}
|
||||
|
||||
/// note | Технические детали
|
||||
Если проверить новую (фальшивую) базу данных `fake_users_db`, то можно увидеть, как теперь выглядит хэшированный пароль: `"$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW"`.
|
||||
Если проверить новую (фальшивую) базу данных `fake_users_db`, то можно увидеть, как теперь выглядит хэшированный пароль: `"$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc"`.
|
||||
///
|
||||
|
||||
## Работа с JWT токенами
|
||||
## Работа с JWT токенами { #handle-jwt-tokens }
|
||||
|
||||
Импортируйте установленные модули.
|
||||
|
||||
@@ -140,13 +140,13 @@ $ openssl rand -hex 32
|
||||
|
||||
Создайте переменную для срока действия токена.
|
||||
|
||||
Определите Pydantic Model, которая будет использоваться для формирования ответа на запрос на получение токена.
|
||||
Определите Pydantic-модель, которая будет использоваться для формирования ответа на запрос на получение токена.
|
||||
|
||||
Создайте служебную функцию для генерации нового токена доступа.
|
||||
|
||||
{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,79:87] *}
|
||||
|
||||
## Обновление зависимостей
|
||||
## Обновление зависимостей { #update-the-dependencies }
|
||||
|
||||
Обновите `get_current_user` для получения того же токена, что и раньше, но на этот раз с использованием JWT-токенов.
|
||||
|
||||
@@ -156,7 +156,7 @@ $ openssl rand -hex 32
|
||||
|
||||
{* ../../docs_src/security/tutorial004_an_py310.py hl[90:107] *}
|
||||
|
||||
## Обновление *операции пути* `/token`
|
||||
## Обновление *операции пути* `/token` { #update-the-token-path-operation }
|
||||
|
||||
Создайте `timedelta` со временем истечения срока действия токена.
|
||||
|
||||
@@ -164,7 +164,7 @@ $ openssl rand -hex 32
|
||||
|
||||
{* ../../docs_src/security/tutorial004_an_py310.py hl[118:133] *}
|
||||
|
||||
### Технические подробности о JWT ключе `sub`
|
||||
### Технические подробности о JWT ключе `sub` { #technical-details-about-the-jwt-subject-sub }
|
||||
|
||||
В спецификации JWT говорится, что существует ключ `sub`, содержащий субъект токена.
|
||||
|
||||
@@ -186,7 +186,7 @@ JWT может использоваться и для других целей,
|
||||
|
||||
Важно помнить, что ключ `sub` должен иметь уникальный идентификатор для всего приложения и представлять собой строку.
|
||||
|
||||
## Проверка в действии
|
||||
## Проверка в действии { #check-it }
|
||||
|
||||
Запустите сервер и перейдите к документации: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
|
||||
|
||||
@@ -201,7 +201,7 @@ JWT может использоваться и для других целей,
|
||||
Username: `johndoe`
|
||||
Password: `secret`
|
||||
|
||||
/// check | Заметка
|
||||
/// check | Проверка
|
||||
Обратите внимание, что нигде в коде не используется открытый текст пароля "`secret`", мы используем только его хэшированную версию.
|
||||
///
|
||||
|
||||
@@ -225,10 +225,10 @@ Password: `secret`
|
||||
<img src="/img/tutorial/security/image10.png">
|
||||
|
||||
/// note | Техническая информация
|
||||
Обратите внимание на заголовок `Authorization`, значение которого начинается с `Bearer`.
|
||||
Обратите внимание на HTTP-заголовок `Authorization`, значение которого начинается с `Bearer `.
|
||||
///
|
||||
|
||||
## Продвинутое использование `scopes`
|
||||
## Продвинутое использование `scopes` { #advanced-usage-with-scopes }
|
||||
|
||||
В OAuth2 существует понятие "диапазоны" ("`scopes`").
|
||||
|
||||
@@ -236,9 +236,9 @@ Password: `secret`
|
||||
|
||||
Затем вы можете передать этот токен непосредственно пользователю или третьей стороне для взаимодействия с вашим API с определенным набором ограничений.
|
||||
|
||||
О том, как их использовать и как они интегрированы в **FastAPI**, читайте далее в **Руководстве пользователя**.
|
||||
О том, как их использовать и как они интегрированы в **FastAPI**, читайте далее в **Расширенном руководстве пользователя**.
|
||||
|
||||
## Резюме
|
||||
## Резюме { #recap }
|
||||
|
||||
С учетом того, что вы видели до сих пор, вы можете создать безопасное приложение **FastAPI**, используя такие стандарты, как OAuth2 и JWT.
|
||||
|
||||
@@ -252,10 +252,10 @@ Password: `secret`
|
||||
|
||||
Он предоставляет вам полную свободу действий, позволяя выбирать то, что лучше всего подходит для вашего проекта.
|
||||
|
||||
Вы можете напрямую использовать многие хорошо поддерживаемые и широко распространенные пакеты, такие как `passlib` и `PyJWT`, поскольку **FastAPI** не требует сложных механизмов для интеграции внешних пакетов.
|
||||
Вы можете напрямую использовать многие хорошо поддерживаемые и широко распространенные пакеты, такие как `pwdlib` и `PyJWT`, поскольку **FastAPI** не требует сложных механизмов для интеграции внешних пакетов.
|
||||
|
||||
Напротив, он предоставляет инструменты, позволяющие максимально упростить этот процесс без ущерба для гибкости, надежности и безопасности.
|
||||
|
||||
При этом вы можете использовать и реализовывать безопасные стандартные протоколы, такие как OAuth2, относительно простым способом.
|
||||
|
||||
В **Руководстве пользователя** вы можете узнать больше о том, как использовать "диапазоны" ("`scopes`") OAuth2 для создания более точно настроенной системы разрешений в соответствии с теми же стандартами. OAuth2 с диапазонами - это механизм, используемый многими крупными провайдерами сервиса аутентификации, такими как Facebook, Google, GitHub, Microsoft, X (Twitter) и др., для авторизации сторонних приложений на взаимодействие с их API от имени их пользователей.
|
||||
В **Расширенном руководстве пользователя** вы можете узнать больше о том, как использовать "диапазоны" ("`scopes`") OAuth2 для создания более точно настроенной системы разрешений в соответствии с теми же стандартами. OAuth2 с диапазонами — это механизм, используемый многими крупными провайдерами сервиса аутентификации, такими как Facebook, Google, GitHub, Microsoft, X (Twitter) и др., для авторизации сторонних приложений на взаимодействие с их API от имени их пользователей.
|
||||
|
||||
@@ -1,59 +1,58 @@
|
||||
# Простая авторизация по протоколу OAuth2 с токеном типа Bearer
|
||||
# Простая авторизация OAuth2 с паролем и «Bearer» { #simple-oauth2-with-password-and-bearer }
|
||||
|
||||
Теперь, отталкиваясь от предыдущей главы, добавим недостающие части, чтобы получить безопасную систему.
|
||||
Теперь, отталкиваясь от предыдущей главы, добавим недостающие части, чтобы получить полный поток безопасности.
|
||||
|
||||
## Получение `имени пользователя` и `пароля`
|
||||
## Получение `username` и `password` { #get-the-username-and-password }
|
||||
|
||||
Для получения `имени пользователя` и `пароля` мы будем использовать утилиты безопасности **FastAPI**.
|
||||
Для получения `username` и `password` мы будем использовать утилиты безопасности **FastAPI**.
|
||||
|
||||
Протокол OAuth2 определяет, что при использовании "аутентификации по паролю" (которую мы и используем) клиент/пользователь должен передавать поля `username` и `password` в полях формы.
|
||||
OAuth2 определяет, что при использовании "password flow" (аутентификация по паролю - именно его мы используем) клиент/пользователь должен передавать поля `username` и `password` в полях формы.
|
||||
|
||||
В спецификации сказано, что поля должны быть названы именно так. Поэтому `user-name` или `email` работать не будут.
|
||||
|
||||
Но не волнуйтесь, вы можете показать его конечным пользователям во фронтенде в том виде, в котором хотите.
|
||||
Но не волнуйтесь, вы можете показать это конечным пользователям во фронтенде в том виде, в котором хотите.
|
||||
|
||||
А ваши модели баз данных могут использовать любые другие имена.
|
||||
|
||||
Но при авторизации согласно спецификации, требуется использовать именно эти имена, что даст нам возможность воспользоваться встроенной системой документации API.
|
||||
Но для логин-операции пути нам нужно использовать именно эти имена, чтобы быть совместимыми со спецификацией (и иметь возможность, например, использовать встроенную систему документации API).
|
||||
|
||||
В спецификации также указано, что `username` и `password` должны передаваться в виде данных формы (так что никакого JSON здесь нет).
|
||||
|
||||
### Oбласть видимости (scope)
|
||||
### `scope` { #scope }
|
||||
|
||||
В спецификации также говорится, что клиент может передать еще одно поле формы "`scope`".
|
||||
В спецификации также говорится, что клиент может передать еще одно поле формы — `scope`.
|
||||
|
||||
Имя поля формы - `scope` (в единственном числе), но на самом деле это длинная строка, состоящая из отдельных областей видимости (scopes), разделенных пробелами.
|
||||
Имя поля формы — `scope` (в единственном числе), но на самом деле это длинная строка, состоящая из отдельных "scopes", разделенных пробелами.
|
||||
|
||||
Каждая "область видимости" (scope) - это просто строка (без пробелов).
|
||||
Каждый "scope" — это просто строка (без пробелов).
|
||||
|
||||
Обычно они используются для указания уровней доступа, например:
|
||||
|
||||
* `users:read` или `users:write` являются распространенными примерами.
|
||||
* `users:read` или `users:write` — распространенные примеры.
|
||||
* `instagram_basic` используется Facebook / Instagram.
|
||||
* `https://www.googleapis.com/auth/drive` используется компанией Google.
|
||||
* `https://www.googleapis.com/auth/drive` используется Google.
|
||||
|
||||
/// info | Дополнительнаяя информация
|
||||
В OAuth2 "scope" - это просто строка, которая уточняет уровень доступа.
|
||||
/// info | Дополнительная информация
|
||||
В OAuth2 "scope" — это просто строка, которая указывает требуемое конкретное разрешение.
|
||||
|
||||
Не имеет значения, содержит ли он другие символы, например `:`, или является ли он URL.
|
||||
Не имеет значения, содержит ли она другие символы, например `:`, или является ли это URL.
|
||||
|
||||
Эти детали зависят от конкретной реализации.
|
||||
Эти детали зависят от реализации.
|
||||
|
||||
Для OAuth2 это просто строки.
|
||||
///
|
||||
|
||||
## Код получения `имени пользователя` и `пароля`
|
||||
## Код для получения `username` и `password` { #code-to-get-the-username-and-password }
|
||||
|
||||
Для решения задачи давайте воспользуемся утилитами, предоставляемыми **FastAPI**.
|
||||
Теперь воспользуемся утилитами, предоставляемыми **FastAPI**, чтобы обработать это.
|
||||
|
||||
### `OAuth2PasswordRequestForm`
|
||||
|
||||
Сначала импортируйте `OAuth2PasswordRequestForm` и затем используйте ее как зависимость с `Depends` в *эндпоинте* `/token`:
|
||||
### `OAuth2PasswordRequestForm` { #oauth2passwordrequestform }
|
||||
|
||||
Сначала импортируйте `OAuth2PasswordRequestForm` и затем используйте её как зависимость с `Depends` в операции пути для `/token`:
|
||||
|
||||
{* ../../docs_src/security/tutorial003_an_py310.py hl[4,78] *}
|
||||
|
||||
`OAuth2PasswordRequestForm` - это класс для использования в качестве зависимости для *функции обрабатывающей эндпоинт*, который определяет тело формы со следующими полями:
|
||||
`OAuth2PasswordRequestForm` — это зависимость-класс, которая объявляет тело формы со следующими полями:
|
||||
|
||||
* `username`.
|
||||
* `password`.
|
||||
@@ -61,51 +60,51 @@
|
||||
* Необязательное поле `grant_type`.
|
||||
|
||||
/// tip | Подсказка
|
||||
По спецификации OAuth2 поле `grant_type` является обязательным и содержит фиксированное значение `password`, но `OAuth2PasswordRequestForm` не обеспечивает этого.
|
||||
По спецификации OAuth2 поле `grant_type` обязательно и содержит фиксированное значение `password`, но `OAuth2PasswordRequestForm` это не проверяет строго.
|
||||
|
||||
Если вам необходимо использовать `grant_type`, воспользуйтесь `OAuth2PasswordRequestFormStrict` вместо `OAuth2PasswordRequestForm`.
|
||||
Если вам нужно это строгое требование, используйте `OAuth2PasswordRequestFormStrict` вместо `OAuth2PasswordRequestForm`.
|
||||
///
|
||||
|
||||
* Необязательное поле `client_id` (в нашем примере он не нужен).
|
||||
* Необязательное поле `client_secret` (в нашем примере он не нужен).
|
||||
* Необязательное поле `client_id` (в нашем примере оно не нужно).
|
||||
* Необязательное поле `client_secret` (в нашем примере оно не нужно).
|
||||
|
||||
/// info | Дополнительная информация
|
||||
Форма `OAuth2PasswordRequestForm` не является специальным классом для **FastAPI**, как `OAuth2PasswordBearer`.
|
||||
`OAuth2PasswordRequestForm` — это не специальный класс для **FastAPI**, как `OAuth2PasswordBearer`.
|
||||
|
||||
`OAuth2PasswordBearer` указывает **FastAPI**, что это схема безопасности. Следовательно, она будет добавлена в OpenAPI.
|
||||
`OAuth2PasswordBearer` сообщает **FastAPI**, что это схема безопасности. Поэтому она добавляется в OpenAPI соответствующим образом.
|
||||
|
||||
Но `OAuth2PasswordRequestForm` - это всего лишь класс зависимости, который вы могли бы написать самостоятельно или вы могли бы объявить параметры `Form` напрямую.
|
||||
А `OAuth2PasswordRequestForm` — это просто зависимость-класс, которую вы могли бы написать сами, или вы могли бы объявить параметры `Form` напрямую.
|
||||
|
||||
Но, поскольку это распространённый вариант использования, он предоставляется **FastAPI** напрямую, просто чтобы облегчить задачу.
|
||||
Но так как это распространённый вариант использования, он предоставлен **FastAPI** напрямую, чтобы упростить задачу.
|
||||
///
|
||||
|
||||
### Использование данных формы
|
||||
### Использование данных формы { #use-the-form-data }
|
||||
|
||||
/// tip | Подсказка
|
||||
В экземпляре зависимого класса `OAuth2PasswordRequestForm` атрибут `scope`, состоящий из одной длинной строки, разделенной пробелами, заменен на атрибут `scopes`, состоящий из списка отдельных строк, каждая из которых соответствует определенному уровню доступа.
|
||||
У экземпляра зависимости `OAuth2PasswordRequestForm` не будет атрибута `scope` с длинной строкой, разделенной пробелами. Вместо этого будет атрибут `scopes` со списком отдельных строк — по одной для каждого переданного scope.
|
||||
|
||||
В данном примере мы не используем `scopes`, но если вам это необходимо, то такая функциональность имеется.
|
||||
В данном примере мы не используем `scopes`, но если вам это необходимо, функциональность есть.
|
||||
///
|
||||
|
||||
Теперь получим данные о пользователе из (ненастоящей) базы данных, используя `username` из поля формы.
|
||||
|
||||
Если такого пользователя нет, то мы возвращаем ошибку "неверное имя пользователя или пароль".
|
||||
Если такого пользователя нет, то мы возвращаем ошибку "Incorrect username or password" (неверное имя пользователя или пароль).
|
||||
|
||||
Для ошибки мы используем исключение `HTTPException`:
|
||||
Для ошибки используем исключение `HTTPException`:
|
||||
|
||||
{* ../../docs_src/security/tutorial003_an_py310.py hl[3,79:81] *}
|
||||
|
||||
### Проверка пароля
|
||||
### Проверка пароля { #check-the-password }
|
||||
|
||||
На данный момент у нас есть данные о пользователе из нашей базы данных, но мы еще не проверили пароль.
|
||||
|
||||
Давайте сначала поместим эти данные в модель Pydantic `UserInDB`.
|
||||
Давайте сначала поместим эти данные в Pydantic-модель `UserInDB`.
|
||||
|
||||
Ни в коем случае нельзя сохранять пароли в открытом виде, поэтому мы будем использовать (пока что ненастоящую) систему хеширования паролей.
|
||||
Никогда нельзя сохранять пароли в открытом виде, поэтому мы будем использовать (пока что ненастоящую) систему хеширования паролей.
|
||||
|
||||
Если пароли не совпадают, мы возвращаем ту же ошибку.
|
||||
|
||||
#### Хеширование паролей
|
||||
#### Хеширование паролей { #password-hashing }
|
||||
|
||||
"Хеширование" означает: преобразование некоторого содержимого (в данном случае пароля) в последовательность байтов (просто строку), которая выглядит как тарабарщина.
|
||||
|
||||
@@ -113,19 +112,19 @@
|
||||
|
||||
Но преобразовать тарабарщину обратно в пароль невозможно.
|
||||
|
||||
##### Зачем использовать хеширование паролей
|
||||
##### Зачем использовать хеширование паролей { #why-use-password-hashing }
|
||||
|
||||
Если ваша база данных будет украдена, то у вора не будет паролей пользователей в открытом виде, только хэши.
|
||||
Если вашу базу данных украдут, у злоумышленника не будет паролей пользователей в открытом виде, только хэши.
|
||||
|
||||
Таким образом, вор не сможет использовать эти же пароли в другой системе (поскольку многие пользователи используют одни и те же пароли повсеместно, это было бы опасно).
|
||||
Таким образом, он не сможет попробовать использовать эти же пароли в другой системе (поскольку многие пользователи используют один и тот же пароль повсеместно, это было бы опасно).
|
||||
|
||||
{* ../../docs_src/security/tutorial003_an_py310.py hl[82:85] *}
|
||||
|
||||
#### Про `**user_dict`
|
||||
#### Про `**user_dict` { #about-user-dict }
|
||||
|
||||
`UserInDB(**user_dict)` означает:
|
||||
|
||||
*Передавать ключи и значения `user_dict` непосредственно в качестве аргументов ключ-значение, что эквивалентно:*
|
||||
*Передать ключи и значения `user_dict` непосредственно как аргументы ключ-значение, эквивалентно:*
|
||||
|
||||
```Python
|
||||
UserInDB(
|
||||
@@ -138,23 +137,23 @@ UserInDB(
|
||||
```
|
||||
|
||||
/// info | Дополнительная информация
|
||||
Более полное объяснение `**user_dict` можно найти в [документации к **Дополнительным моделям**](../extra-models.md#about-user_indict){.internal-link target=_blank}.
|
||||
Более полное объяснение `**user_dict` можно найти в [документации к **Дополнительным моделям**](../extra-models.md#about-user-in-dict){.internal-link target=_blank}.
|
||||
///
|
||||
|
||||
## Возврат токена
|
||||
## Возврат токена { #return-the-token }
|
||||
|
||||
Ответ эндпоинта `token` должен представлять собой объект в формате JSON.
|
||||
Ответ операции пути `/token` должен быть объектом JSON.
|
||||
|
||||
Он должен иметь `token_type`. В нашем случае, поскольку мы используем токены типа "Bearer", тип токена должен быть "`bearer`".
|
||||
В нём должен быть `token_type`. В нашем случае, поскольку мы используем токены типа "Bearer", тип токена должен быть `bearer`.
|
||||
|
||||
И в нем должна быть строка `access_token`, содержащая наш токен доступа.
|
||||
И в нём должен быть `access_token` — строка, содержащая наш токен доступа.
|
||||
|
||||
В этом простом примере мы нарушим все правила безопасности, и будем считать, что имя пользователя (username) полностью соответствует токену (token)
|
||||
В этом простом примере мы намеренно поступим небезопасно и вернём тот же `username` в качестве токена.
|
||||
|
||||
/// tip | Подсказка
|
||||
В следующей главе мы рассмотрим реальную защищенную реализацию с хешированием паролей и токенами <abbr title="JSON Web Tokens">JWT</abbr>.
|
||||
В следующей главе вы увидите реальную защищённую реализацию с хешированием паролей и токенами <abbr title="JSON Web Tokens – JSON веб-токены">JWT</abbr>.
|
||||
|
||||
Но пока давайте остановимся на необходимых нам деталях.
|
||||
Но пока давайте сосредоточимся на необходимых нам деталях.
|
||||
///
|
||||
|
||||
{* ../../docs_src/security/tutorial003_an_py310.py hl[87] *}
|
||||
@@ -162,50 +161,50 @@ UserInDB(
|
||||
/// tip | Подсказка
|
||||
Согласно спецификации, вы должны возвращать JSON с `access_token` и `token_type`, как в данном примере.
|
||||
|
||||
Это то, что вы должны сделать сами в своем коде и убедиться, что вы используете эти JSON-ключи.
|
||||
Это то, что вы должны сделать сами в своём коде и убедиться, что вы используете именно эти JSON-ключи.
|
||||
|
||||
Это практически единственное, что нужно не забывать делать самостоятельно, чтобы следовать требованиям спецификации.
|
||||
Это практически единственное, о чём нужно не забыть, чтобы соответствовать спецификациям.
|
||||
|
||||
Все остальное за вас сделает **FastAPI**.
|
||||
Остальное за вас сделает **FastAPI**.
|
||||
///
|
||||
|
||||
## Обновление зависимостей
|
||||
## Обновление зависимостей { #update-the-dependencies }
|
||||
|
||||
Теперь мы обновим наши зависимости.
|
||||
|
||||
Мы хотим получить значение `current_user` *только* если этот пользователь активен.
|
||||
Мы хотим получить `current_user` только если этот пользователь активен.
|
||||
|
||||
Поэтому мы создаем дополнительную зависимость `get_current_active_user`, которая, в свою очередь, использует в качестве зависимости `get_current_user`.
|
||||
Поэтому мы создаём дополнительную зависимость `get_current_active_user`, которая, в свою очередь, использует в качестве зависимости `get_current_user`.
|
||||
|
||||
Обе эти зависимости просто вернут HTTP-ошибку, если пользователь не существует или неактивен.
|
||||
|
||||
Таким образом, в нашем эндпоинте мы получим пользователя только в том случае, если он существует, правильно аутентифицирован и активен:
|
||||
Таким образом, в нашей операции пути мы получим пользователя только в том случае, если он существует, корректно аутентифицирован и активен:
|
||||
|
||||
{* ../../docs_src/security/tutorial003_an_py310.py hl[58:66,69:74,94] *}
|
||||
|
||||
/// info | Дополнительная информация
|
||||
Дополнительный заголовок `WWW-Authenticate` со значением `Bearer`, который мы здесь возвращаем, также является частью спецификации.
|
||||
Дополнительный HTTP-заголовок `WWW-Authenticate` со значением `Bearer`, который мы здесь возвращаем, также является частью спецификации.
|
||||
|
||||
Ответ сервера с HTTP-кодом 401 "UNAUTHORIZED" должен также возвращать заголовок `WWW-Authenticate`.
|
||||
Любой HTTP статус-код 401 "UNAUTHORIZED" должен также возвращать заголовок `WWW-Authenticate`.
|
||||
|
||||
В случае с bearer-токенами (наш случай) значение этого заголовка должно быть `Bearer`.
|
||||
|
||||
На самом деле этот дополнительный заголовок можно пропустить и все будет работать.
|
||||
Фактически, этот дополнительный заголовок можно опустить, и всё будет работать.
|
||||
|
||||
Но он приведён здесь для соответствия спецификации.
|
||||
Но он приведён здесь для соответствия спецификациям.
|
||||
|
||||
Кроме того, могут существовать инструменты, которые ожидают его и могут использовать, и это может быть полезно для вас или ваших пользователей сейчас или в будущем.
|
||||
Кроме того, могут существовать инструменты, которые ожидают его и могут использовать, и это может быть полезно для вас или ваших пользователей — сейчас или в будущем.
|
||||
|
||||
В этом и заключается преимущество стандартов...
|
||||
///
|
||||
|
||||
## Посмотим как это работает
|
||||
## Посмотрим, как это работает { #see-it-in-action }
|
||||
|
||||
Откроем интерактивную документацию: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
|
||||
Откройте интерактивную документацию: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
|
||||
|
||||
### Аутентификация
|
||||
### Аутентификация { #authenticate }
|
||||
|
||||
Нажмите кнопку "Авторизация".
|
||||
Нажмите кнопку "Authorize".
|
||||
|
||||
Используйте учётные данные:
|
||||
|
||||
@@ -215,13 +214,15 @@ UserInDB(
|
||||
|
||||
<img src="/img/tutorial/security/image04.png">
|
||||
|
||||
После авторизации в системе вы увидите следующее:
|
||||
После аутентификации вы увидите следующее:
|
||||
|
||||
<img src="/img/tutorial/security/image05.png">
|
||||
|
||||
### Получение собственных пользовательских данных
|
||||
### Получение собственных пользовательских данных { #get-your-own-user-data }
|
||||
|
||||
Теперь, используя операцию `GET` с путем `/users/me`, вы получите данные пользователя, например:
|
||||
Теперь используйте операцию `GET` с путём `/users/me`.
|
||||
|
||||
Вы получите свои пользовательские данные, например:
|
||||
|
||||
```JSON
|
||||
{
|
||||
@@ -235,7 +236,7 @@ UserInDB(
|
||||
|
||||
<img src="/img/tutorial/security/image06.png">
|
||||
|
||||
Если щелкнуть на значке замка и выйти из системы, а затем попытаться выполнить ту же операцию ещё раз, то будет выдана ошибка HTTP 401:
|
||||
Если щёлкнуть на значке замка и выйти из системы, а затем попытаться выполнить ту же операцию ещё раз, будет выдана ошибка HTTP 401:
|
||||
|
||||
```JSON
|
||||
{
|
||||
@@ -243,17 +244,17 @@ UserInDB(
|
||||
}
|
||||
```
|
||||
|
||||
### Неактивный пользователь
|
||||
### Неактивный пользователь { #inactive-user }
|
||||
|
||||
Теперь попробуйте пройти аутентификацию с неактивным пользователем:
|
||||
Теперь попробуйте с неактивным пользователем, аутентифицируйтесь с:
|
||||
|
||||
Пользователь: `alice`
|
||||
|
||||
Пароль: `secret2`
|
||||
|
||||
И попробуйте использовать операцию `GET` с путем `/users/me`.
|
||||
И попробуйте использовать операцию `GET` с путём `/users/me`.
|
||||
|
||||
Вы получите ошибку "Inactive user", как тут:
|
||||
Вы получите ошибку "Inactive user", как здесь:
|
||||
|
||||
```JSON
|
||||
{
|
||||
@@ -261,12 +262,12 @@ UserInDB(
|
||||
}
|
||||
```
|
||||
|
||||
## Резюме
|
||||
## Резюме { #recap }
|
||||
|
||||
Теперь у вас есть инструменты для реализации полноценной системы безопасности на основе `имени пользователя` и `пароля` для вашего API.
|
||||
Теперь у вас есть инструменты для реализации полноценной системы безопасности на основе `username` и `password` для вашего API.
|
||||
|
||||
Используя эти средства, можно сделать систему безопасности совместимой с любой базой данных, с любым пользователем или моделью данных.
|
||||
Используя эти средства, можно сделать систему безопасности совместимой с любой базой данных и с любой пользовательской или моделью данных.
|
||||
|
||||
Единственным недостатком нашей системы является то, что она всё ещё не защищена.
|
||||
Единственная деталь, которой не хватает, — система пока ещё не "защищена" по-настоящему.
|
||||
|
||||
В следующей главе вы увидите, как использовать библиотеку безопасного хеширования паролей и токены <abbr title="JSON Web Tokens">JWT</abbr>.
|
||||
В следующей главе вы увидите, как использовать библиотеку безопасного хеширования паролей и токены <abbr title="JSON Web Tokens – JSON веб-токены">JWT</abbr>.
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
# SQL (реляционные) базы данных
|
||||
# SQL (реляционные) базы данных { #sql-relational-databases }
|
||||
|
||||
**FastAPI** не требует использования реляционной базы данных. Вы можете воспользоваться любой базой данных, которой хотите.
|
||||
**FastAPI** не требует использовать SQL (реляционную) базу данных. Но вы можете использовать любую базу данных, которую хотите.
|
||||
|
||||
В этом разделе мы продемонстрируем, как работать с <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">SQLModel</a>.
|
||||
Здесь мы рассмотрим пример с использованием <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">SQLModel</a>.
|
||||
|
||||
Библиотека **SQLModel** построена на основе <a href="https://www.sqlalchemy.org/" class="external-link" target="_blank">SQLAlchemy</a> и Pydantic. Она была разработана автором **FastAPI** специально для приложений на основе FastAPI, которые используют **реляционные базы данных**.
|
||||
**SQLModel** построен поверх <a href="https://www.sqlalchemy.org/" class="external-link" target="_blank">SQLAlchemy</a> и Pydantic. Его создал тот же автор, что и **FastAPI**, чтобы он идеально подходил для приложений FastAPI, которым нужны **SQL базы данных**.
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Вы можете воспользоваться любой библиотекой для работы с реляционными (SQL) или нереляционными (NoSQL) базами данных. (Их ещё называют <abbr title="ORM = Object Relational Mapper, этот термин для библиотеки, в которой классы представляют SQL-таблицы, а экземпляры классов представляют строки в этих таблицах.">**ORM**</abbr> библиотеками). FastAPI не принуждает вас к использованию чего-либо конкретного. 😎
|
||||
Вы можете использовать любую другую библиотеку для работы с SQL или NoSQL базами данных (иногда их называют <abbr title="Object Relational Mapper – Объектно-реляционный маппер: термин для библиотеки, где некоторые классы представляют SQL-таблицы, а экземпляры представляют строки в этих таблицах">"ORMs"</abbr>), FastAPI ничего не навязывает. 😎
|
||||
|
||||
///
|
||||
|
||||
В основе SQLModel лежит SQLAlchemy, поэтому вы спокойно можете использовать любую базу данных, поддерживаемую SQLAlchemy (и, соответственно, поддерживаемую SQLModel), например:
|
||||
Так как SQLModel основан на SQLAlchemy, вы можете легко использовать **любую поддерживаемую** SQLAlchemy базу данных (а значит, и поддерживаемую SQLModel), например:
|
||||
|
||||
* PostgreSQL
|
||||
* MySQL
|
||||
@@ -20,21 +20,21 @@
|
||||
* Oracle
|
||||
* Microsoft SQL Server, и т.д.
|
||||
|
||||
В данном примере мы будем использовать базу данных **SQLite**, т.к. она состоит из единственного файла и поддерживается встроенными библиотеками Python. Таким образом, вы сможете скопировать данный пример и запустить его как он есть.
|
||||
В этом примере мы будем использовать **SQLite**, потому что она использует один файл и имеет встроенную поддержку в Python. Так что вы можете скопировать этот пример и запустить его как есть.
|
||||
|
||||
В дальнейшем, для продакшн-версии вашего приложения, возможно, вам стоит использовать серверную базу данных, например, **PostgreSQL**.
|
||||
Позже, для продакшн-приложения, возможно, вы захотите использовать серверную базу данных, например **PostgreSQL**.
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Существует официальный генератор проектов на **FastAPI** и **PostgreSQL**, который также включает frontend и дополнительные инструменты <a href="https://github.com/fastapi/full-stack-fastapi-template" class="external-link" target="_blank">https://github.com/fastapi/full-stack-fastapi-template</a>
|
||||
Существует официальный генератор проектов на **FastAPI** и **PostgreSQL**, включающий frontend и другие инструменты: <a href="https://github.com/fastapi/full-stack-fastapi-template" class="external-link" target="_blank">https://github.com/fastapi/full-stack-fastapi-template</a>
|
||||
|
||||
///
|
||||
|
||||
Это очень простое и короткое руководство, поэтому, если вы хотите узнать о базах данных в целом, об SQL, разобраться с более продвинутым функционалом, то воспользуйтесь <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">документацией SQLModel</a>.
|
||||
Это очень простое и короткое руководство. Если вы хотите узнать больше о базах данных в целом, об SQL или о более продвинутых возможностях, обратитесь к <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">документации SQLModel</a>.
|
||||
|
||||
## Установка `SQLModel`
|
||||
## Установка `SQLModel` { #install-sqlmodel }
|
||||
|
||||
Создайте виртуальное окружение [virtual environment](../virtual-environments.md){.internal-link target=_blank}, активируйте его и установите `sqlmodel`:
|
||||
Сначала убедитесь, что вы создали [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активировали его и затем установили `sqlmodel`:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -45,110 +45,109 @@ $ pip install sqlmodel
|
||||
|
||||
</div>
|
||||
|
||||
## Создание приложения с единственной моделью
|
||||
## Создание приложения с единственной моделью { #create-the-app-with-a-single-model }
|
||||
|
||||
Мы начнем с создания наиболее простой первой версии нашего приложения с одной единственной моделью **SQLModel**.
|
||||
Сначала мы создадим самую простую первую версию приложения с одной моделью **SQLModel**.
|
||||
|
||||
В дальнейшем с помощью **дополнительных моделей** мы его улучшим и сделаем более безопасным и универсальным. 🤓
|
||||
Позже мы улучшим его, повысив безопасность и универсальность, добавив **несколько моделей**. 🤓
|
||||
|
||||
### Создание моделей
|
||||
### Создание моделей { #create-models }
|
||||
|
||||
Импортируйте `SQLModel` и создайте модель базы данных:
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[1:11] hl[7:11] *}
|
||||
|
||||
Класс `Hero` очень напоминает модель Pydantic (фактически, под капотом, *это и есть модель Pydantic*).
|
||||
Класс `Hero` очень похож на модель Pydantic (фактически, под капотом, *это и есть модель Pydantic*).
|
||||
|
||||
Но есть и некоторые различия
|
||||
Есть несколько отличий:
|
||||
|
||||
* `table=True` для SQLModel означает, что это *модель-таблица*, которая должна представлять **таблицу** в реляционной базе данных. Это не просто *модель данных* (в отличие от обычного класса в Pydantic).
|
||||
* `table=True` сообщает SQLModel, что это *модель-таблица*, она должна представлять **таблицу** в SQL базе данных, это не просто *модель данных* (как обычный класс Pydantic).
|
||||
|
||||
* `Field(primary_key=True)` для SQLModel означает, что поле `id` является первичным ключом в таблице базы данных (вы можете подробнее узнать о первичных ключах баз данных в документации по SQLModel).
|
||||
* `Field(primary_key=True)` сообщает SQLModel, что `id` — это **первичный ключ** в SQL базе данных (подробнее о первичных ключах можно узнать в документации SQLModel).
|
||||
|
||||
Тип `int | None` сигнализирует для SQLModel, что столбец таблицы базы данных должен иметь тип `INTEGER`, или иметь пустое значение `NULL`.
|
||||
Благодаря типу `int | None`, SQLModel будет знать, что этот столбец должен быть `INTEGER` в SQL базе данных и должен допускать значение `NULL`.
|
||||
|
||||
* `Field(index=True)` для SQLModel означает, что нужно создать **SQL индекс** для данного столбца. Это обеспечит более быстрый поиск при чтении данных, отфильтрованных по данному столбцу.
|
||||
* `Field(index=True)` сообщает SQLModel, что нужно создать **SQL индекс** для этого столбца, что позволит быстрее выполнять выборки при чтении данных, отфильтрованных по этому столбцу.
|
||||
|
||||
SQLModel будет знать, что данные типа `str`, будут представлены в базе данных как `TEXT` (или `VARCHAR`, в зависимости от типа базы данных).
|
||||
SQLModel будет знать, что объявленное как `str` станет SQL-столбцом типа `TEXT` (или `VARCHAR`, в зависимости от базы данных).
|
||||
|
||||
### Создание соединения с базой данных (Engine)
|
||||
### Создание Engine { #create-an-engine }
|
||||
|
||||
В SQLModel объект соединения `engine` (по сути это `Engine` из SQLAlchemy) **содержит пул соединений** к базе данных.
|
||||
Объект `engine` в SQLModel (под капотом это `engine` из SQLAlchemy) **удерживает соединения** с базой данных.
|
||||
|
||||
Для обеспечения всех подключений приложения к одной базе данных нужен только один объект соединения `engine`.
|
||||
У вас должен быть **один объект `engine`** для всей кодовой базы, чтобы подключаться к одной и той же базе данных.
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[14:18] hl[14:15,17:18] *}
|
||||
|
||||
Использование настройки `check_same_thread=False` позволяет FastAPI использовать одну и ту же SQLite базу данных в различных потоках (threads). Это необходимо, когда **один запрос** использует **более одного потока** (например, в зависимостях).
|
||||
Параметр `check_same_thread=False` позволяет FastAPI использовать одну и ту же базу данных SQLite в разных потоках. Это необходимо, так как **один запрос** может использовать **больше одного потока** (например, в зависимостях).
|
||||
|
||||
Не беспокойтесь, учитывая структуру кода, мы позже позаботимся о том, чтобы использовать **отдельную SQLModel-сессию на каждый отдельный запрос**, это как раз то, что пытается обеспечить `check_same_thread`.
|
||||
Не волнуйтесь, с такой структурой кода мы позже обеспечим использование **одной *сессии* SQLModel на запрос**, по сути именно этого и добивается `check_same_thread`.
|
||||
|
||||
### Создание таблиц
|
||||
### Создание таблиц { #create-the-tables }
|
||||
|
||||
Далее мы добавляем функцию, использующую `SQLModel.metadata.create_all(engine)`, для того, чтобы создать **таблицы** для каждой из **моделей таблицы**.
|
||||
Далее мы добавим функцию, которая использует `SQLModel.metadata.create_all(engine)`, чтобы **создать таблицы** для всех *моделей-таблиц*.
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[21:22] hl[21:22] *}
|
||||
|
||||
### Создание зависимости Session
|
||||
### Создание зависимости Session { #create-a-session-dependency }
|
||||
|
||||
Сессия базы данных (**`Session`**) хранит **объекты в памяти** и отслеживает любые необходимые изменения в данных, а затем **использует `engine`** для коммуникации с базой данных.
|
||||
**`Session`** хранит **объекты в памяти** и отслеживает необходимые изменения в данных, затем **использует `engine`** для общения с базой данных.
|
||||
|
||||
Мы создадим FastAPI-**зависимость** с помощью `yield`, которая будет создавать новую сессию (Session) для каждого запроса. Это как раз и обеспечит использование отдельной сессии на каждый отдельный запрос. 🤓
|
||||
Мы создадим **зависимость** FastAPI с `yield`, которая будет предоставлять новую `Session` для каждого запроса. Это и обеспечивает использование одной сессии на запрос. 🤓
|
||||
|
||||
Затем мы создадим объявленную (`Annotated`) зависимость `SessionDep`. Мы сделаем это для того, чтобы упростить остальной код, который будет использовать эту зависимость.
|
||||
Затем мы создадим объявленную (`Annotated`) зависимость `SessionDep`, чтобы упростить остальной код, который будет использовать эту зависимость.
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[25:30] hl[25:27,30] *}
|
||||
|
||||
### Создание таблиц базы данных при запуске приложения
|
||||
### Создание таблиц базы данных при старте { #create-database-tables-on-startup }
|
||||
|
||||
Мы будем создавать таблицы базы данных при запуске приложения.
|
||||
Мы создадим таблицы базы данных при запуске приложения.
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[32:37] hl[35:37] *}
|
||||
|
||||
В данном примере мы создаем таблицы при наступлении события запуска приложения.
|
||||
Здесь мы создаём таблицы в обработчике события запуска приложения.
|
||||
|
||||
В продуктовом приложении вы, скорее всего, будете использовать скрипт для миграции базы данных, который выполняется перед запуском приложения. 🤓
|
||||
Для продакшна вы, вероятно, будете использовать скрипт миграций, который выполняется до запуска приложения. 🤓
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
В SQLModel будут включены утилиты миграции, входящие в состав Alembic, но на данный момент вы просто можете использовать
|
||||
<a href="https://alembic.sqlalchemy.org/en/latest/" class="external-link" target="_blank">Alembic</a> напрямую.
|
||||
В SQLModel появятся утилиты миграций - обёртки над Alembic, но пока вы можете использовать <a href="https://alembic.sqlalchemy.org/en/latest/" class="external-link" target="_blank">Alembic</a> напрямую.
|
||||
|
||||
///
|
||||
|
||||
### Создание героя (Hero)
|
||||
### Создание героя (Hero) { #create-a-hero }
|
||||
|
||||
Каждая модель в SQLModel является также моделью Pydantic, поэтому вы можете использовать её при **объявлении типов**, точно также, как и модели Pydantic.
|
||||
Так как каждая модель SQLModel также является моделью Pydantic, вы можете использовать её в тех же **аннотациях типов**, в которых используете модели Pydantic.
|
||||
|
||||
Например, при объявлении параметра типа `Hero`, она будет считана из **тела JSON**.
|
||||
Например, если вы объявите параметр типа `Hero`, он будет прочитан из **JSON body (тела запроса)**.
|
||||
|
||||
Точно также, вы можете использовать её при объявлении типа значения, возвращаемого функцией, и тогда структурированные данные будут отображены через пользовательский интерфейс автоматически сгенерированной документации FastAPI.
|
||||
Аналогично вы можете объявить её как **тип возвращаемого значения** функции, и тогда форма данных отобразится в автоматически сгенерированном UI документации API.
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[40:45] hl[40:45] *}
|
||||
|
||||
Мы используем зависимость `SessionDep` (сессию базы данных) для того, чтобы добавить нового героя `Hero` в объект сессии (`Session`), сохранить изменения в базе данных, обновить данные героя и затем вернуть их.
|
||||
Здесь мы используем зависимость `SessionDep` (это `Session`), чтобы добавить нового `Hero` в экземпляр `Session`, зафиксировать изменения в базе данных, обновить данные в `hero` и затем вернуть его.
|
||||
|
||||
### Чтение данных о героях
|
||||
### Чтение героев { #read-heroes }
|
||||
|
||||
Мы можем **читать** данные героев из базы данных с помощью `select()`. Мы можем включить `limit` и `offset` для постраничного считывания результатов.
|
||||
Мы можем **читать** записи `Hero` из базы данных с помощью `select()`. Можно добавить `limit` и `offset` для постраничного вывода результатов.
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[48:55] hl[51:52,54] *}
|
||||
|
||||
### Чтение данных отдельного героя
|
||||
### Чтение одного героя { #read-one-hero }
|
||||
|
||||
Мы можем прочитать данные отдельного героя (`Hero`).
|
||||
Мы можем **прочитать** одного `Hero`.
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[58:63] hl[60] *}
|
||||
|
||||
### Удаление данных героя
|
||||
### Удаление героя { #delete-a-hero }
|
||||
|
||||
Мы также можем удалить героя `Hero` из базы данных.
|
||||
Мы также можем **удалить** `Hero`.
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[66:73] hl[71] *}
|
||||
|
||||
### Запуск приложения
|
||||
### Запуск приложения { #run-the-app }
|
||||
|
||||
Вы можете запустить приложение следующим образом:
|
||||
Вы можете запустить приложение:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -160,49 +159,49 @@ $ fastapi dev main.py
|
||||
|
||||
</div>
|
||||
|
||||
Далее перейдите в пользовательский интерфейс API `/docs`. Вы увидите, что **FastAPI** использует модели для создания документации API. Эти же модели используются для сериализации и проверки данных.
|
||||
Затем перейдите в UI `/docs`. Вы увидите, что **FastAPI** использует эти **модели** для **документирования** API, а также для **сериализации** и **валидации** данных.
|
||||
|
||||
<div class="screenshot">
|
||||
<img src="/img/tutorial/sql-databases/image01.png">
|
||||
</div>
|
||||
|
||||
## Добавление в приложение дополнительных (вспомогательных) моделей
|
||||
## Обновление приложения с несколькими моделями { #update-the-app-with-multiple-models }
|
||||
|
||||
Теперь давайте проведём **рефакторинг** нашего приложения, чтобы сделать его более безопасным и более универсальным.
|
||||
Теперь давайте немного **отрефакторим** приложение, чтобы повысить **безопасность** и **универсальность**.
|
||||
|
||||
Обратите внимание, что на данном этапе наше приложение позволяет на уровне клиента определять `id` создаваемого героя (`Hero`). 😱
|
||||
Если вы посмотрите на предыдущую версию, в UI видно, что до сих пор клиент мог сам задавать `id` создаваемого `Hero`. 😱
|
||||
|
||||
Мы не можем этого допустить, т.к. существует риск переписать уже присвоенные `id` в базе данных. Присвоение `id` должно происходить **на уровне бэкэнда (backend)** или **на уровне базы данных**, но никак **не на уровне клиента**.
|
||||
Так делать нельзя, иначе они могли бы перезаписать `id`, который уже присвоен в БД. Решение по `id` должно приниматься **бэкендом** или **базой данных**, а **не клиентом**.
|
||||
|
||||
Кроме того, мы создаем секретное имя `secret_name` для героя, но пока что, мы возвращаем его повсеместно, и это слабо напоминает **секретность**... 😅
|
||||
Кроме того, мы создаём для героя `secret_name`, но пока что возвращаем его повсюду — это не очень **секретно**... 😅
|
||||
|
||||
Мы поправим это с помощью нескольких дополнительных (вспомогательных) моделей. Вот где SQLModel по-настоящему покажет себя. ✨
|
||||
Мы исправим это, добавив несколько **дополнительных моделей**. Здесь SQLModel раскроется во всей красе. ✨
|
||||
|
||||
### Создание дополнительных моделей
|
||||
### Создание нескольких моделей { #create-multiple-models }
|
||||
|
||||
В **SQLModel**, любая модель с параметром `table=True` является **моделью таблицы**.
|
||||
В **SQLModel** любая модель с `table=True` — это **модель-таблица**.
|
||||
|
||||
Любая модель, не содержащая `table=True` является **моделью данных**, это по сути обычные модели Pydantic (с несколько расширенным функционалом). 🤓
|
||||
Любая модель без `table=True` — это **модель данных**, по сути обычная модель Pydantic (с парой небольших дополнений). 🤓
|
||||
|
||||
С помощью SQLModel мы можем использовать **наследование**, что поможет нам **избежать дублирования** всех полей.
|
||||
С SQLModel мы можем использовать **наследование**, чтобы **избежать дублирования** полей.
|
||||
|
||||
#### Базовый класс `HeroBase`
|
||||
#### `HeroBase` — базовый класс { #herobase-the-base-class }
|
||||
|
||||
Давайте начнём с модели `HeroBase`, которая содержит поля, общие для всех моделей:
|
||||
Начнём с модели `HeroBase`, которая содержит **общие поля** для всех моделей:
|
||||
|
||||
* `name`
|
||||
* `age`
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:9] hl[7:9] *}
|
||||
|
||||
#### Модель таблицы `Hero`
|
||||
#### `Hero` — *модель-таблица* { #hero-the-table-model }
|
||||
|
||||
Далее давайте создадим **модель таблицы** `Hero` с дополнительными полями, которых может не быть в других моделях:
|
||||
Далее создадим `Hero`, фактическую *модель-таблицу*, с **дополнительными полями**, которых может не быть в других моделях:
|
||||
|
||||
* `id`
|
||||
* `secret_name`
|
||||
|
||||
Модель `Hero` наследует от `HeroBase`, и поэтому включает также поля из `HeroBase`. Таким образом, все поля, содержащиеся в `Hero`, будут следующими:
|
||||
Так как `Hero` наследуется от `HeroBase`, он **также** имеет **поля**, объявленные в `HeroBase`, поэтому все поля `Hero`:
|
||||
|
||||
* `id`
|
||||
* `name`
|
||||
@@ -211,25 +210,25 @@ $ fastapi dev main.py
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:14] hl[12:14] *}
|
||||
|
||||
#### Публичная модель данных `HeroPublic`
|
||||
#### `HeroPublic` — публичная *модель данных* { #heropublic-the-public-data-model }
|
||||
|
||||
Далее мы создадим модель `HeroPublic`. Мы будем возвращать её клиентам API.
|
||||
Далее мы создадим модель `HeroPublic`, именно она будет **возвращаться** клиентам API.
|
||||
|
||||
Она включает в себя те же поля, что и `HeroBase`, и, соответственно, поле `secret_name` в ней отсутствует.
|
||||
У неё те же поля, что и у `HeroBase`, поэтому она не включает `secret_name`.
|
||||
|
||||
Наконец-то личность наших героев защищена! 🥷
|
||||
|
||||
В модели `HeroPublic` также объявляется поле `id: int`. Мы как бы заключаем договоренность с API клиентом, на то, что передаваемые данные всегда должны содержать поле `id`, и это поле должно содержать целое число (и никогда не содержать `None`).
|
||||
Также здесь заново объявляется `id: int`. Тем самым мы заключаем **контракт** с клиентами API: они всегда могут рассчитывать, что поле `id` присутствует и это `int` (никогда не `None`).
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Модель ответа, гарантирующая наличие поля со значением типа `int` (не `None`), очень полезна при разработке API клиентов. Определенность в передаваемых данных может обеспечить написание более простого кода.
|
||||
Гарантия того, что в модели ответа значение всегда присутствует и это `int` (не `None`), очень полезна для клиентов API — так можно писать гораздо более простой код.
|
||||
|
||||
Также **автоматически генерируемые клиенты** будут иметь более простой интерфейс. И в результате жизнь разработчиков, использующих ваш API, станет значительно легче. 😎
|
||||
Кроме того, **автоматически сгенерированные клиенты** будут иметь более простые интерфейсы, и разработчикам, взаимодействующим с вашим API, будет работать значительно комфортнее. 😎
|
||||
|
||||
///
|
||||
|
||||
`HeroPublic` содержит все поля `HeroBase`, а также поле `id`, объявленное как `int` (не `None`):
|
||||
Все поля `HeroPublic` такие же, как в `HeroBase`, а `id` объявлен как `int` (не `None`):
|
||||
|
||||
* `id`
|
||||
* `name`
|
||||
@@ -237,23 +236,23 @@ $ fastapi dev main.py
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:18] hl[17:18] *}
|
||||
|
||||
#### Модель для создания героя `HeroCreate`
|
||||
#### `HeroCreate` — *модель данных* для создания героя { #herocreate-the-data-model-to-create-a-hero }
|
||||
|
||||
Сейчас мы создадим модель `HeroCreate`. Эта модель будет использоваться для проверки данных, переданных клиентом.
|
||||
Теперь создадим модель `HeroCreate`, она будет **валидировать** данные от клиентов.
|
||||
|
||||
Она содержит те же поля, что и `HeroBase`, а также поле `secret_name`.
|
||||
У неё те же поля, что и у `HeroBase`, а также есть `secret_name`.
|
||||
|
||||
Теперь, при создании нового героя, клиенты будут передавать секретное имя `secret_name`, которое будет сохранено в базе данных, но не будет возвращено в ответе API клиентам.
|
||||
Теперь, когда клиенты **создают нового героя**, они будут отправлять `secret_name`, он сохранится в базе данных, но не будет возвращаться клиентам в API.
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Вот как нужно работать с **паролями**: получайте их, но не возвращайте их через API.
|
||||
Так следует обрабатывать **пароли**: принимать их, но не возвращать в API.
|
||||
|
||||
Также хэшируйте значения паролей перед тем, как их сохранить. Ни в коем случае не храните пароли в открытом виде, как обычный текст.
|
||||
Также перед сохранением значения паролей нужно **хэшировать**, **никогда не храните их в открытом виде**.
|
||||
|
||||
///
|
||||
|
||||
Поля модели `HeroCreate`:
|
||||
Поля `HeroCreate`:
|
||||
|
||||
* `name`
|
||||
* `age`
|
||||
@@ -261,15 +260,15 @@ $ fastapi dev main.py
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:22] hl[21:22] *}
|
||||
|
||||
#### Модель для обновления данных героя `HeroUpdate`
|
||||
#### `HeroUpdate` — *модель данных* для обновления героя { #heroupdate-the-data-model-to-update-a-hero }
|
||||
|
||||
В предыдущих версиях нашей программы мы не могли обновить данные героя, теперь, воспользовавшись дополнительными моделями, мы сможем это сделать. 🎉
|
||||
В предыдущей версии приложения у нас не было способа **обновлять героя**, но теперь, с **несколькими моделями**, мы можем это сделать. 🎉
|
||||
|
||||
Модель данных `HeroUpdate` в некотором смысле особенная. Она содержит все те же поля, что и модель создания героя, но все поля модели являются **необязательными**. (Все они имеют значение по умолчанию.) Таким образом, при обновлении данных героя, вам достаточно передать только те поля, которые требуют изменения.
|
||||
*Модель данных* `HeroUpdate` особенная: у неё **те же поля**, что и для создания нового героя, но все поля **необязательные** (у всех есть значение по умолчанию). Таким образом, при обновлении героя можно отправлять только те поля, которые нужно изменить.
|
||||
|
||||
Поскольку **все поля по сути меняются** (теперь тип каждого поля допускает значение `None` и значение по умолчанию `None`), мы должны их **объявить заново**.
|
||||
Поскольку **фактически меняются все поля** (их тип теперь включает `None`, и по умолчанию они равны `None`), нам нужно **переобъявить** их.
|
||||
|
||||
Фактически, нам не нужно наследоваться от `HeroBase`, потому что мы будем заново объявлять все поля. Я оставлю наследование просто для поддержания общего стиля, но оно (наследование) здесь необязательно. 🤷
|
||||
Наследоваться от `HeroBase` не обязательно, так как мы заново объявляем все поля. Я оставлю наследование для единообразия, но это не необходимо. Скорее дело вкуса. 🤷
|
||||
|
||||
Поля `HeroUpdate`:
|
||||
|
||||
@@ -279,59 +278,59 @@ $ fastapi dev main.py
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:28] hl[25:28] *}
|
||||
|
||||
### Создание героя с помощью `HeroCreate` и возвращение результатов с помощью `HeroPublic`
|
||||
### Создание с `HeroCreate` и возврат `HeroPublic` { #create-with-herocreate-and-return-a-heropublic }
|
||||
|
||||
Теперь, когда у нас есть дополнительные модели, мы можем обновить те части приложения, которые их используют.
|
||||
Теперь, когда у нас есть **несколько моделей**, мы можем обновить части приложения, которые их используют.
|
||||
|
||||
Вместе c запросом на создание героя мы получаем объект данных `HeroCreate`, и создаем на его основе объект модели таблицы `Hero`.
|
||||
Мы получаем в запросе *модель данных* `HeroCreate` и на её основе создаём *модель-таблицу* `Hero`.
|
||||
|
||||
Созданный объект *модели таблицы* `Hero` будет иметь все поля, переданные клиентом, а также поле `id`, сгенерированное базой данных.
|
||||
Новая *модель-таблица* `Hero` будет иметь поля, отправленные клиентом, а также `id`, сгенерированный базой данных.
|
||||
|
||||
Далее функция вернёт объект *модели таблицы* `Hero`. Но поскольку, мы объявили `HeroPublic` как модель ответа, то **FastAPI** будет использовать именно её для проверки и сериализации данных.
|
||||
Затем возвращаем из функции ту же *модель-таблицу* `Hero` как есть. Но так как мы объявили `response_model` с *моделью данных* `HeroPublic`, **FastAPI** использует `HeroPublic` для валидации и сериализации данных.
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[56:62] hl[56:58] *}
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Теперь мы используем модель ответа `response_model=HeroPublic`, вместо того, чтобы объявить тип возвращаемого значения как `-> HeroPublic`. Мы это делаем потому, что тип возвращаемого значения не относится к `HeroPublic`.
|
||||
Теперь мы используем `response_model=HeroPublic` вместо **аннотации типа возвращаемого значения** `-> HeroPublic`, потому что фактически возвращаемое значение — это *не* `HeroPublic`.
|
||||
|
||||
Если бы мы объявили тип возвращаемого значения как `-> HeroPublic`, то редактор и линтер начали бы ругаться (и вполне справедливо), что возвращаемое значение принадлежит типу `Hero`, а совсем не `HeroPublic`.
|
||||
Если бы мы объявили `-> HeroPublic`, ваш редактор кода и линтер справедливо пожаловались бы, что вы возвращаете `Hero`, а не `HeroPublic`.
|
||||
|
||||
Объявляя модель ответа в `response_model`, мы как бы говорим **FastAPI**: делай свое дело, не вмешиваясь в аннотацию типов и не полагаясь на помощь редактора или других инструментов.
|
||||
Объявляя модель в `response_model`, мы говорим **FastAPI** сделать своё дело, не вмешиваясь в аннотации типов и работу редактора кода и других инструментов.
|
||||
|
||||
///
|
||||
|
||||
### Чтение данных героев с помощью `HeroPublic`
|
||||
### Чтение героев с `HeroPublic` { #read-heroes-with-heropublic }
|
||||
|
||||
Мы можем проделать то же самое **для чтения данных** героев. Мы применим модель ответа `response_model=list[HeroPublic]`, и тем самым обеспечим правильную проверку и сериализацию данных.
|
||||
Аналогично мы можем **читать** `Hero` — снова используем `response_model=list[HeroPublic]`, чтобы данные валидировались и сериализовались корректно.
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[65:72] hl[65] *}
|
||||
|
||||
### Чтение данных отдельного героя с помощью `HeroPublic`
|
||||
### Чтение одного героя с `HeroPublic` { #read-one-hero-with-heropublic }
|
||||
|
||||
Мы можем **прочитать** данные отдельного героя:
|
||||
Мы можем **прочитать** одного героя:
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[75:80] hl[77] *}
|
||||
|
||||
### Обновление данных героя с помощью `HeroUpdate`
|
||||
### Обновление героя с `HeroUpdate` { #update-a-hero-with-heroupdate }
|
||||
|
||||
Мы можем **обновить данные героя**. Для этого мы воспользуемся HTTP методом `PATCH`.
|
||||
Мы можем **обновить героя**. Для этого используем HTTP операцию `PATCH`.
|
||||
|
||||
В коде мы получаем объект словаря `dict` с данными, переданными клиентом (т.е. **только c данными, переданными клиентом**, исключая любые значения, которые могли бы быть там только потому, что они являются значениями по умолчанию). Для того чтобы сделать это, мы воспользуемся опцией `exclude_unset=True`. В этом главная хитрость. 🪄
|
||||
В коде мы получаем `dict` со всеми данными, отправленными клиентом — **только с данными, отправленными клиентом**, исключая любые значения, которые были бы там лишь как значения по умолчанию. Для этого мы используем `exclude_unset=True`. Это главный трюк. 🪄
|
||||
|
||||
Затем мы применим `hero_db.sqlmodel_update(hero_data)`, и обновим `hero_db`, использовав данные `hero_data`.
|
||||
Затем мы используем `hero_db.sqlmodel_update(hero_data)`, чтобы обновить `hero_db` данными из `hero_data`.
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[83:93] hl[83:84,88:89] *}
|
||||
|
||||
### Удалим героя ещё раз
|
||||
### Снова удаление героя { #delete-a-hero-again }
|
||||
|
||||
Операция **удаления** героя практически не меняется.
|
||||
Операция **удаления** героя остаётся практически прежней.
|
||||
|
||||
В данном случае желание *`отрефакторить всё`* остаётся неудовлетворенным. 😅
|
||||
Желание *«отрефакторить всё»* на этот раз останется неудовлетворённым. 😅
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[96:103] hl[101] *}
|
||||
|
||||
### Снова запустим приложение
|
||||
### Снова запустим приложение { #run-the-app-again }
|
||||
|
||||
Вы можете снова запустить приложение:
|
||||
|
||||
@@ -345,14 +344,14 @@ $ fastapi dev main.py
|
||||
|
||||
</div>
|
||||
|
||||
Если вы перейдете в пользовательский интерфейс API `/docs`, то вы увидите, что он был обновлен, и больше не принимает параметра `id` от клиента при создании нового героя, и т.д.
|
||||
Если вы перейдёте в UI API `/docs`, вы увидите, что он обновился: теперь при создании героя он не ожидает получить `id` от клиента и т. д.
|
||||
|
||||
<div class="screenshot">
|
||||
<img src="/img/tutorial/sql-databases/image02.png">
|
||||
</div>
|
||||
|
||||
## Резюме
|
||||
## Резюме { #recap }
|
||||
|
||||
Вы можете использовать <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">**SQLModel**</a> для взаимодействия с реляционными базами данных, а также для упрощения работы с **моделями данных** и **моделями таблиц**.
|
||||
Вы можете использовать <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">**SQLModel**</a> для взаимодействия с SQL базой данных и упростить код с помощью *моделей данных* и *моделей-таблиц*.
|
||||
|
||||
Вы можете узнать гораздо больше информации в документации по **SQLModel**. Там вы найдете более подробное <a href="https://sqlmodel.tiangolo.com/tutorial/fastapi/" class="external-link" target="_blank">мини-руководство по использованию SQLModel с **FastAPI**</a>. 🚀
|
||||
Гораздо больше вы можете узнать в документации **SQLModel**, там есть более подробный мини-<a href="https://sqlmodel.tiangolo.com/tutorial/fastapi/" class="external-link" target="_blank">туториал по использованию SQLModel с **FastAPI**</a>. 🚀
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# Статические Файлы
|
||||
# Статические Файлы { #static-files }
|
||||
|
||||
Вы можете предоставлять статические файлы автоматически из директории, используя `StaticFiles`.
|
||||
|
||||
## Использование `StaticFiles`
|
||||
## Использование `StaticFiles` { #use-staticfiles }
|
||||
|
||||
* Импортируйте `StaticFiles`.
|
||||
* "Примонтируйте" экземпляр `StaticFiles()` с указанием определенной директории.
|
||||
* "Примонтируйте" экземпляр `StaticFiles()` к определённому пути.
|
||||
|
||||
{* ../../docs_src/static_files/tutorial001.py hl[2,6] *}
|
||||
|
||||
@@ -17,16 +17,16 @@
|
||||
|
||||
///
|
||||
|
||||
### Что такое "Монтирование"
|
||||
### Что такое "Монтирование" { #what-is-mounting }
|
||||
|
||||
"Монтирование" означает добавление полноценного "независимого" приложения в определенную директорию, которое затем обрабатывает все подпути.
|
||||
"Монтирование" означает добавление полноценного "независимого" приложения на определённый путь, которое затем обрабатывает все подпути.
|
||||
|
||||
Это отличается от использования `APIRouter`, так как примонтированное приложение является полностью независимым.
|
||||
OpenAPI и документация из вашего главного приложения не будет содержать ничего из примонтированного приложения, и т.д.
|
||||
OpenAPI и документация из вашего главного приложения не будут содержать ничего из примонтированного приложения, и т.д.
|
||||
|
||||
Вы можете прочитать больше об этом в **Расширенном руководстве пользователя**.
|
||||
Вы можете прочитать больше об этом в [Расширенном руководстве пользователя](../advanced/index.md){.internal-link target=_blank}.
|
||||
|
||||
## Детали
|
||||
## Детали { #details }
|
||||
|
||||
Первый параметр `"/static"` относится к подпути, по которому это "подприложение" будет "примонтировано". Таким образом, любой путь начинающийся со `"/static"` будет обработан этим приложением.
|
||||
|
||||
@@ -36,6 +36,6 @@ OpenAPI и документация из вашего главного прил
|
||||
|
||||
Все эти параметры могут отличаться от "`static`", настройте их в соответствии с вашими нуждами и конкретными деталями вашего собственного приложения.
|
||||
|
||||
## Больше информации
|
||||
## Больше информации { #more-info }
|
||||
|
||||
Для получения дополнительной информации о деталях и настройках ознакомьтесь с <a href="https://www.starlette.io/staticfiles/" class="external-link" target="_blank">Документацией Starlette о статических файлах</a>.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Тестирование
|
||||
# Тестирование { #testing }
|
||||
|
||||
Благодаря <a href="https://www.starlette.io/testclient/" class="external-link" target="_blank">Starlette</a>, тестировать приложения **FastAPI** легко и приятно.
|
||||
|
||||
@@ -6,13 +6,17 @@
|
||||
|
||||
Используя эти инструменты, Вы можете напрямую задействовать <a href="https://docs.pytest.org/" class="external-link" target="_blank">pytest</a> с **FastAPI**.
|
||||
|
||||
## Использование класса `TestClient`
|
||||
## Использование класса `TestClient` { #using-testclient }
|
||||
|
||||
/// info | Информация
|
||||
|
||||
Для использования класса `TestClient` необходимо установить библиотеку <a href="https://www.python-httpx.org" class="external-link" target="_blank">`httpx`</a>.
|
||||
Для использования класса `TestClient` сначала установите <a href="https://www.python-httpx.org" class="external-link" target="_blank">`httpx`</a>.
|
||||
|
||||
Например, так: `pip install httpx`.
|
||||
Убедитесь, что Вы создали [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активировали его, а затем установили пакет, например:
|
||||
|
||||
```console
|
||||
$ pip install httpx
|
||||
```
|
||||
|
||||
///
|
||||
|
||||
@@ -42,7 +46,7 @@
|
||||
|
||||
Также можно написать `from starlette.testclient import TestClient`.
|
||||
|
||||
**FastAPI** предоставляет тот же самый `starlette.testclient` как `fastapi.testclient`. Это всего лишь небольшое удобство для Вас, как разработчика.
|
||||
**FastAPI** предоставляет тот же самый `starlette.testclient` как `fastapi.testclient`. Это всего лишь небольшое удобство для Вас, как разработчика. Но он берётся напрямую из Starlette.
|
||||
|
||||
///
|
||||
|
||||
@@ -52,13 +56,13 @@
|
||||
|
||||
///
|
||||
|
||||
## Разделение тестов и приложения
|
||||
## Разделение тестов { #separating-tests }
|
||||
|
||||
В реальном приложении Вы, вероятно, разместите тесты в отдельном файле.
|
||||
|
||||
Кроме того, Ваше приложение **FastAPI** может состоять из нескольких файлов, модулей и т.п.
|
||||
|
||||
### Файл приложения **FastAPI**
|
||||
### Файл приложения **FastAPI** { #fastapi-app-file }
|
||||
|
||||
Допустим, структура файлов Вашего приложения похожа на ту, что описана на странице [Более крупные приложения](bigger-applications.md){.internal-link target=_blank}:
|
||||
|
||||
@@ -69,12 +73,12 @@
|
||||
│ └── main.py
|
||||
```
|
||||
|
||||
Здесь файл `main.py` является "точкой входа" в Ваше приложение и содержит инициализацию Вашего приложения **FastAPI**:
|
||||
В файле `main.py` находится Ваше приложение **FastAPI**:
|
||||
|
||||
|
||||
{* ../../docs_src/app_testing/main.py *}
|
||||
|
||||
### Файл тестов
|
||||
### Файл тестов { #testing-file }
|
||||
|
||||
Также у Вас может быть файл `test_main.py` содержащий тесты. Можно разместить тестовый файл и файл приложения в одной директории (в директориях для Python-кода желательно размещать и файл `__init__.py`):
|
||||
|
||||
@@ -93,11 +97,11 @@
|
||||
|
||||
...и писать дальше тесты, как и раньше.
|
||||
|
||||
## Тестирование: расширенный пример
|
||||
## Тестирование: расширенный пример { #testing-extended-example }
|
||||
|
||||
Теперь давайте расширим наш пример и добавим деталей, чтоб посмотреть, как тестировать различные части приложения.
|
||||
|
||||
### Расширенный файл приложения **FastAPI**
|
||||
### Расширенный файл приложения **FastAPI** { #extended-fastapi-app-file }
|
||||
|
||||
Мы продолжим работу с той же файловой структурой, что и ранее:
|
||||
|
||||
@@ -113,7 +117,7 @@
|
||||
|
||||
В нём описана операция `GET`, которая может вернуть ошибку.
|
||||
|
||||
Ещё есть операция `POST` и она тоже может вернуть ошибку.
|
||||
Ещё есть операция `POST`, и она может вернуть несколько ошибок.
|
||||
|
||||
Обе *операции пути* требуют наличия в запросе заголовка `X-Token`.
|
||||
|
||||
@@ -155,7 +159,7 @@
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+ без Annotated
|
||||
//// tab | Python 3.8+ без Annotated
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
@@ -169,7 +173,7 @@
|
||||
|
||||
////
|
||||
|
||||
### Расширенный файл тестов
|
||||
### Расширенный файл тестов { #extended-testing-file }
|
||||
|
||||
Теперь обновим файл `test_main.py`, добавив в него тестов:
|
||||
|
||||
@@ -198,9 +202,11 @@
|
||||
|
||||
///
|
||||
|
||||
## Запуск тестов
|
||||
## Запуск { #run-it }
|
||||
|
||||
Далее Вам нужно установить `pytest`:
|
||||
Далее Вам нужно установить `pytest`.
|
||||
|
||||
Убедитесь, что Вы создали [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активировали его, а затем установили пакет, например:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -214,7 +220,7 @@ $ pip install pytest
|
||||
|
||||
Он автоматически найдёт все файлы и тесты, выполнит их и предоставит Вам отчёт о результатах тестирования.
|
||||
|
||||
Запустите тесты командой `pytest` и увидите результат:
|
||||
Запустите тесты:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
|
||||
@@ -1,71 +1,69 @@
|
||||
# Виртуальная среда
|
||||
# Виртуальные окружения { #virtual-environments }
|
||||
|
||||
При работе с проектами в Python рекомендуется использовать **виртуальную среду разработки** (или какой-нибудь другой подобный механизм). Это нужно для того, чтобы изолировать устанавливаемые пакеты для каждого отдельного проекта.
|
||||
При работе с проектами на Python рекомендуется использовать **виртуальное окружение** (или похожий механизм), чтобы изолировать пакеты, которые вы устанавливаете для каждого проекта.
|
||||
|
||||
/// info | Дополнительная информация
|
||||
|
||||
Если вы уже знакомы с виртуальными средами разработки, знаете как их создавать и использовать, то вы можете свободно пропустить данный раздел. 🤓
|
||||
Если вы уже знакомы с виртуальными окружениями, знаете, как их создавать и использовать, вы можете пропустить этот раздел. 🤓
|
||||
|
||||
///
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
**Виртуальная среда** и **переменная окружения** это две разные вещи.
|
||||
**Виртуальное окружение** — это не то же самое, что **переменная окружения**.
|
||||
|
||||
**Переменная окружения** это системная переменная, которую могут использовать программы.
|
||||
**Переменная окружения** — это переменная в системе, которую могут использовать программы.
|
||||
|
||||
**Виртуальная среда** это папка, содержащая файлы.
|
||||
**Виртуальное окружение** — это директория с файлами внутри.
|
||||
|
||||
///
|
||||
|
||||
/// info | Дополнительная информация
|
||||
|
||||
В этом разделе мы научим вас пользоваться виртуальными средами разработки и расскажем, как они работают.
|
||||
На этой странице вы узнаете, как пользоваться **виртуальными окружениями** и как они работают.
|
||||
|
||||
Если же вы готовы воспользоваться инструментом, **который умеет управлять всем, что касается Python-проектов**,
|
||||
(включая установку Python), то попробуйте <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">uv</a>.
|
||||
Если вы готовы начать использовать **инструмент, который управляет всем** за вас (включая установку Python), попробуйте <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">uv</a>.
|
||||
|
||||
///
|
||||
|
||||
## Создание проекта
|
||||
## Создание проекта { #create-a-project }
|
||||
|
||||
В первую очередь, создайте директорию для вашего проекта.
|
||||
Сначала создайте директорию для вашего проекта.
|
||||
|
||||
Я обычно создаю папку под названием `code` внутри моего домашнего каталога `/home/user`.
|
||||
Обычно я создаю папку с именем `code` в моем домашнем каталоге.
|
||||
|
||||
Затем внутри данной папки я создаю отдельную директорию под каждый свой проект.
|
||||
А внутри неё создаю отдельную директорию для каждого проекта.
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
// Перейдите в домашний каталог
|
||||
$ cd
|
||||
// Создайте отдельную папку под все будущие программные проекты (code)
|
||||
// Создайте директорию для всех ваших проектов с кодом
|
||||
$ mkdir code
|
||||
// Войдите в директорию code
|
||||
// Перейдите в эту директорию code
|
||||
$ cd code
|
||||
// Создайте директрорию под данный проект (awesome-project)
|
||||
// Создайте директорию для этого проекта
|
||||
$ mkdir awesome-project
|
||||
// Перейдите в созданную директорию проекта
|
||||
// Перейдите в директорию проекта
|
||||
$ cd awesome-project
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## Создание виртуальной среды разработки
|
||||
## Создание виртуального окружения { #create-a-virtual-environment }
|
||||
|
||||
Начиная работу с Python-проектом, сразу же создавайте виртуальную среду разработки
|
||||
**<abbr title="есть и другие опции, но мы рассмотрим наиболее простой вариант">внутри вашего проекта</abbr>**.
|
||||
Когда вы начинаете работать над Python‑проектом **впервые**, создайте виртуальное окружение **<abbr title="есть и другие опции, но это простой ориентир">внутри вашего проекта</abbr>**.
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Виртуальная среда разработки создается один раз, и в дальнейшем, работая с проектом, этого больше делать не придется.
|
||||
Делать это нужно **один раз на проект**, не каждый раз, когда вы работаете.
|
||||
|
||||
///
|
||||
|
||||
//// tab | `venv`
|
||||
|
||||
Для создания виртуальной среды вы можете воспользоваться модулем `venv`, который является частью встроенной библиотеки Python.
|
||||
Для создания виртуального окружения вы можете использовать модуль `venv`, который поставляется вместе с Python.
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -77,10 +75,10 @@ $ python -m venv .venv
|
||||
|
||||
/// details | Что делает эта команда?
|
||||
|
||||
* `python`: использовать программу под именем `python`
|
||||
* `-m`: вызывать модуль как скрипт, в следующей инструкции мы скажем какой именно модуль вызвать
|
||||
* `venv`: использовать модуль под названием `venv`, который обычно устанавливается вместе с Python
|
||||
* `.venv`: создать виртуальную среду разработки в новой директории `.venv`
|
||||
* `python`: использовать программу под названием `python`
|
||||
* `-m`: вызвать модуль как скрипт, далее мы укажем, какой модуль вызвать
|
||||
* `venv`: использовать модуль `venv`, который обычно устанавливается вместе с Python
|
||||
* `.venv`: создать виртуальное окружение в новой директории `.venv`
|
||||
|
||||
///
|
||||
|
||||
@@ -88,7 +86,7 @@ $ python -m venv .venv
|
||||
|
||||
//// tab | `uv`
|
||||
|
||||
Если вы установили <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>, то вы можете им воспользоваться для создания виртуальной среды разработки.
|
||||
Если у вас установлен <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>, вы можете использовать его для создания виртуального окружения.
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -100,29 +98,29 @@ $ uv venv
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
По умолчанию `uv` создаст виртуальную среду разработки в папке под названием `.venv`.
|
||||
По умолчанию `uv` создаст виртуальное окружение в директории с именем `.venv`.
|
||||
|
||||
Но вы можете это изменить, передав дополнительный аргумент с именем директории.
|
||||
Но вы можете переопределить это, передав дополнительный аргумент с именем директории.
|
||||
|
||||
///
|
||||
|
||||
////
|
||||
|
||||
Данная команда создаст новую виртуальную среду разработки в папке `.venv`.
|
||||
Эта команда создаст новое виртуальное окружение в директории `.venv`.
|
||||
|
||||
/// details | `.venv` или другое имя?
|
||||
|
||||
Вы можете поместить виртуальную среду разработки в папку с другим именем, но традиционным (конвенциональным) названием является `.venv` .
|
||||
Вы можете создать виртуальное окружение в другой директории, но по соглашению его называют `.venv`.
|
||||
|
||||
///
|
||||
|
||||
## Активация виртуальной среды разработки
|
||||
## Активация виртуального окружения { #activate-the-virtual-environment }
|
||||
|
||||
Активируйте виртуальную среду разработки, и тогда любая запускаемая Python-команда или устанавливаемый пакет будут ее использовать.
|
||||
Активируйте новое виртуальное окружение, чтобы все команды Python и устанавливаемые пакеты использовали именно его.
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
При работе над проектом делайте это **каждый раз** при запуске **новой сессии в терминале**.
|
||||
Делайте это **каждый раз**, когда вы начинаете **новую сессию терминала** для работы над проектом.
|
||||
|
||||
///
|
||||
|
||||
@@ -152,7 +150,7 @@ $ .venv\Scripts\Activate.ps1
|
||||
|
||||
//// tab | Windows Bash
|
||||
|
||||
Или при использовании Bash для Windows (напр. <a href="https://gitforwindows.org/" class="external-link" target="_blank">Git Bash</a>):
|
||||
Или если вы используете Bash для Windows (например, <a href="https://gitforwindows.org/" class="external-link" target="_blank">Git Bash</a>):
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -164,13 +162,21 @@ $ source .venv/Scripts/activate
|
||||
|
||||
////
|
||||
|
||||
## Проверка активации виртуальной среды
|
||||
/// tip | Подсказка
|
||||
|
||||
Проверьте, активна ли виртуальная среда (удостоверимся, что предыдущая команда сработала).
|
||||
Каждый раз, когда вы устанавливаете **новый пакет** в это окружение, **активируйте** окружение снова.
|
||||
|
||||
Это гарантирует, что если вы используете **программу терминала (<abbr title="command line interface – интерфейс командной строки">CLI</abbr>)**, установленную этим пакетом, вы будете использовать именно ту, что из вашего виртуального окружения, а не какую‑то глобально установленную, возможно другой версии, чем вам нужна.
|
||||
|
||||
///
|
||||
|
||||
## Проверка, что виртуальное окружение активно { #check-the-virtual-environment-is-active }
|
||||
|
||||
Проверьте, что виртуальное окружение активно (предыдущая команда сработала).
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Убедитесь в том, что все работает так, как нужно и вы используете именно ту виртуальную среду разработки, которую нужно. Делать это необязательно, но желательно.
|
||||
Это **необязательно**, но это хороший способ **проверить**, что всё работает как ожидается и вы используете запланированное виртуальное окружение.
|
||||
|
||||
///
|
||||
|
||||
@@ -186,7 +192,7 @@ $ which python
|
||||
|
||||
</div>
|
||||
|
||||
Если данная команда показывает, что исполняемый файл `python` (`.venv\bin\python`), находится внутри виртуальной среды вашего проекта (у нас это `awesome-project`), значит все отработало как нужно. 🎉
|
||||
Если отображается исполняемый файл `python` по пути `.venv/bin/python` внутри вашего проекта (в нашем случае `awesome-project`), значит всё сработало. 🎉
|
||||
|
||||
////
|
||||
|
||||
@@ -202,29 +208,29 @@ C:\Users\user\code\awesome-project\.venv\Scripts\python
|
||||
|
||||
</div>
|
||||
|
||||
Если данная команда показывает, что исполняемый файл `python` (`.venv\Scripts\python`), находится внутри виртуальной среды вашего проекта (у нас это `awesome-project`), значит все отработало как нужно. 🎉
|
||||
Если отображается исполняемый файл `python` по пути `.venv\Scripts\python` внутри вашего проекта (в нашем случае `awesome-project`), значит всё сработало. 🎉
|
||||
|
||||
////
|
||||
|
||||
## Обновление `pip`
|
||||
## Обновление `pip` { #upgrade-pip }
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Если вы используете <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>, то вы должны будете его использовать для установки пакетов вместо `pip`, поэтому обновлять `pip` вам ненужно. 😎
|
||||
Если вы используете <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>, то для установки вы будете использовать его вместо `pip`, поэтому обновлять `pip` не нужно. 😎
|
||||
|
||||
///
|
||||
|
||||
Если для установки пакетов вы используете `pip` (он устанавливается по умолчанию вместе с Python), то обновите `pip` до последней версии.
|
||||
Если для установки пакетов вы используете `pip` (он идёт по умолчанию вместе с Python), вам стоит **обновить** его до последней версии.
|
||||
|
||||
Большинство экзотических ошибок, возникающих при установке пакетов, устраняется предварительным обновлением `pip`.
|
||||
Многие экзотические ошибки при установке пакетов решаются простым предварительным обновлением `pip`.
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Обычно это делается только один раз, сразу после создания виртуальной среды разработки.
|
||||
Обычно это делается **один раз**, сразу после создания виртуального окружения.
|
||||
|
||||
///
|
||||
|
||||
Убедитесь в том, что виртуальная среда активирована (с помощью вышеуказанной команды) и запустите следующую команду:
|
||||
Убедитесь, что виртуальное окружение активно (см. команду выше) и запустите:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -236,19 +242,19 @@ $ python -m pip install --upgrade pip
|
||||
|
||||
</div>
|
||||
|
||||
## Добавление `.gitignore`
|
||||
## Добавление `.gitignore` { #add-gitignore }
|
||||
|
||||
Если вы используете **Git** (а вы должны его использовать), то добавьте файл `.gitignore` и исключите из Git всё, что находится в папке `.venv`.
|
||||
Если вы используете **Git** (а вам стоит его использовать), добавьте файл `.gitignore`, чтобы исключить из Git всё, что находится в вашей `.venv`.
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Если для создания виртуальной среды вы используете <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>, то для вас все уже сделано и вы можете пропустить этот шаг. 😎
|
||||
Если вы использовали <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a> для создания виртуального окружения, он уже сделал это за вас — можно пропустить этот шаг. 😎
|
||||
|
||||
///
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Это делается один раз, сразу после создания виртуальной среды разработки.
|
||||
Сделайте это **один раз**, сразу после создания виртуального окружения.
|
||||
|
||||
///
|
||||
|
||||
@@ -262,13 +268,13 @@ $ echo "*" > .venv/.gitignore
|
||||
|
||||
/// details | Что делает эта команда?
|
||||
|
||||
* `echo "*"`: напечатать `*` в консоли (следующий шаг это слегка изменит)
|
||||
* `>`: все что находится слева от `>` не печатать в консоль, но записать в файл находящийся справа от `>`
|
||||
* `.gitignore`: имя файла, в который нужно записать текст.
|
||||
* `echo "*"`: «напечатать» в терминале текст `*` (следующая часть немного меняет поведение)
|
||||
* `>`: всё, что команда слева от `>` выводит в терминал, вместо печати нужно записать в файл, указанный справа от `>`
|
||||
* `.gitignore`: имя файла, в который нужно записать текст
|
||||
|
||||
`*` в Git означает "всё". Т.е. будет проигнорировано всё, что содержится в папке `.venv`.
|
||||
А `*` в Git означает «всё». То есть будет игнорироваться всё в директории `.venv`.
|
||||
|
||||
Данная команда создаст файл `.gitignore` следующего содержания:
|
||||
Эта команда создаст файл `.gitignore` со следующим содержимым:
|
||||
|
||||
```gitignore
|
||||
*
|
||||
@@ -276,25 +282,25 @@ $ echo "*" > .venv/.gitignore
|
||||
|
||||
///
|
||||
|
||||
## Установка пакетов
|
||||
## Установка пакетов { #install-packages }
|
||||
|
||||
После установки виртуальной среды, вы можете устанавливать в нее пакеты.
|
||||
После активации окружения вы можете устанавливать в него пакеты.
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Сделайте это **один раз**, при установке или обновлении пакетов, нужных вашему проекту.
|
||||
Сделайте это **один раз** при установке или обновлении пакетов, необходимых вашему проекту.
|
||||
|
||||
Если вам понадобится обновить версию пакета или добавить новый пакет, то вы должны будете **сделать это снова**.
|
||||
Если вам нужно обновить версию или добавить новый пакет, вы **сделаете это снова**.
|
||||
|
||||
///
|
||||
|
||||
### Установка пакетов напрямую
|
||||
### Установка пакетов напрямую { #install-packages-directly }
|
||||
|
||||
Если вы торопитесь и не хотите объявлять зависимости проекта в отдельном файле, то вы можете установить их напрямую.
|
||||
Если вы торопитесь и не хотите объявлять зависимости проекта в отдельном файле, вы можете установить их напрямую.
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Объявление пакетов, которые использует ваш проект, и их версий в отдельном файле (например, в `requirements.txt` или в `pyproject.toml`) - это отличная идея.
|
||||
Очень хорошая идея — указать используемые вашим проектом пакеты и их версии в файле (например, `requirements.txt` или `pyproject.toml`).
|
||||
|
||||
///
|
||||
|
||||
@@ -314,7 +320,7 @@ $ pip install "fastapi[standard]"
|
||||
|
||||
//// tab | `uv`
|
||||
|
||||
Если вы используете <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>:
|
||||
Если у вас установлен <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -327,9 +333,9 @@ $ uv pip install "fastapi[standard]"
|
||||
|
||||
////
|
||||
|
||||
### Установка из `requirements.txt`
|
||||
### Установка из `requirements.txt` { #install-from-requirements-txt }
|
||||
|
||||
Если у вас есть `requirements.txt`, то вы можете использовать его для установки пакетов.
|
||||
Если у вас есть `requirements.txt`, вы можете использовать его для установки пакетов.
|
||||
|
||||
//// tab | `pip`
|
||||
|
||||
@@ -346,7 +352,7 @@ $ pip install -r requirements.txt
|
||||
|
||||
//// tab | `uv`
|
||||
|
||||
Если вы используете <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>:
|
||||
Если у вас установлен <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -361,7 +367,7 @@ $ uv pip install -r requirements.txt
|
||||
|
||||
/// details | `requirements.txt`
|
||||
|
||||
`requirements.txt` с парочкой пакетов внутри выглядит приблизительно так:
|
||||
`requirements.txt` с некоторыми пакетами может выглядеть так:
|
||||
|
||||
```requirements.txt
|
||||
fastapi[standard]==0.113.0
|
||||
@@ -370,9 +376,9 @@ pydantic==2.8.0
|
||||
|
||||
///
|
||||
|
||||
## Запуск программы
|
||||
## Запуск вашей программы { #run-your-program }
|
||||
|
||||
После активации виртуальной среды разработки вы можете запустить свою программу и она будет использовать версию Python и пакеты, установленные в виртуальной среде.
|
||||
После активации виртуального окружения вы можете запустить свою программу, и она будет использовать Python из вашего виртуального окружения вместе с установленными там пакетами.
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -384,9 +390,9 @@ Hello World
|
||||
|
||||
</div>
|
||||
|
||||
## Настройка редактора
|
||||
## Настройка вашего редактора кода { #configure-your-editor }
|
||||
|
||||
Вероятно, вы захотите воспользоваться редактором. Убедитесь, что вы настроили его на использование той самой виртуальной среды, которую вы создали. (Скорее всего, она автоматически будет обнаружена). Это позволит вам использовать авто-завершение и выделение ошибок в редакторе.
|
||||
Скорее всего, вы будете использовать редактор кода. Убедитесь, что вы настроили его на использование того же виртуального окружения, которое вы создали (обычно он определяет его автоматически), чтобы получить автозавершение и подсветку ошибок.
|
||||
|
||||
Например:
|
||||
|
||||
@@ -395,13 +401,13 @@ Hello World
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Обычно это делается один раз, при создании виртуальной среды разработки.
|
||||
Обычно это нужно сделать только **один раз**, при создании виртуального окружения.
|
||||
|
||||
///
|
||||
|
||||
## Деактивация виртуальной среды разработки
|
||||
## Деактивация виртуального окружения { #deactivate-the-virtual-environment }
|
||||
|
||||
По окончании работы над проектом вы можете деактивировать виртуальную среду.
|
||||
Когда закончите работу над проектом, вы можете **деактивировать** виртуальное окружение.
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -411,55 +417,55 @@ $ deactivate
|
||||
|
||||
</div>
|
||||
|
||||
Таким образом, при запуске `python`, будет использована версия `python` установленная глобально, а не из этой виртуальной среды вместе с установленными в ней пакетами.
|
||||
Таким образом, при запуске `python` он не будет пытаться запускаться из этого виртуального окружения с установленными там пакетами.
|
||||
|
||||
## Все готово к работе
|
||||
## Готово к работе { #ready-to-work }
|
||||
|
||||
Теперь вы готовы к тому, чтобы начать работу над своим проектом.
|
||||
Теперь вы готовы начать работать над своим проектом.
|
||||
|
||||
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Хотите разобраться со всем, что написано выше?
|
||||
Хотите понять, что это всё было выше?
|
||||
|
||||
Продолжайте читать. 👇🤓
|
||||
|
||||
///
|
||||
|
||||
## Зачем использовать виртуальную среду?
|
||||
## Зачем нужны виртуальные окружения { #why-virtual-environments }
|
||||
|
||||
Для работы с FastAPI вам потребуется установить <a href="https://www.python.org/" class="external-link" target="_blank">Python</a>.
|
||||
Чтобы работать с FastAPI, вам нужно установить <a href="https://www.python.org/" class="external-link" target="_blank">Python</a>.
|
||||
|
||||
После этого вам нужно будет **установить** FastAPI и другие **пакеты**, которые вы собираетесь использовать.
|
||||
После этого вам нужно будет **установить** FastAPI и другие **пакеты**, которые вы хотите использовать.
|
||||
|
||||
Для установки пакетов обычно используют `pip`, который устанавливается вместе с Python (или же используют альтернативные решения).
|
||||
Для установки пакетов обычно используют команду `pip`, которая идет вместе с Python (или альтернативные инструменты).
|
||||
|
||||
Тем не менее, если вы просто используете `pip` напрямую, то пакеты будут установлены в **глобальное Python-окружение** (глобально установленный Python).
|
||||
Тем не менее, если просто использовать `pip` напрямую, пакеты будут установлены в **глобальное окружение Python** (глобально установленный Python).
|
||||
|
||||
### Проблема
|
||||
### Проблема { #the-problem }
|
||||
|
||||
Так в чем же проблема с установкой пакетов в глобальную среду Python?
|
||||
Так в чём проблема установки пакетов в глобальное окружение Python?
|
||||
|
||||
В какой-то момент вам, вероятно, придется писать множество разных программ, которые используют различные пакеты. 😱
|
||||
Со временем вы, вероятно, будете писать много разных программ, зависящих от **разных пакетов**. И некоторые из ваших проектов будут зависеть от **разных версий** одного и того же пакета. 😱
|
||||
|
||||
Например, вы создаете проект `philosophers-stone`, который зависит от пакета под названием **`harry`, версии `1`**. Таким образом, вам нужно установить `harry`.
|
||||
Например, вы можете создать проект `philosophers-stone`, который зависит от пакета **`harry` версии `1`**. Значит, нужно установить `harry`.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
stone(philosophers-stone) -->|requires| harry-1[harry v1]
|
||||
```
|
||||
|
||||
Затем, в какой-то момент, вы создаете другой проект под названием `prisoner-of-azkaban`, и этот проект тоже зависит от `harry`, но он уже использует **`harry` версии `3`**.
|
||||
Затем вы создаёте другой проект `prisoner-of-azkaban`, который тоже зависит от `harry`, но ему нужен **`harry` версии `3`**.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
azkaban(prisoner-of-azkaban) --> |requires| harry-3[harry v3]
|
||||
```
|
||||
|
||||
Проблема заключается в том, что при установке в глобальное окружение, а не в локальную виртуальную среду разработки, вам нужно будет выбирать, какую версию пакета `harry` устанавливать.
|
||||
Проблема в том, что если устанавливать пакеты глобально (в глобальное окружение), а не в локальное **виртуальное окружение**, вам придётся выбирать, какую версию `harry` установить.
|
||||
|
||||
Если вам нужен `philosophers-stone`, то вам нужно сначала установить `harry` версии `1`:
|
||||
Если вы хотите запустить `philosophers-stone`, сначала нужно установить `harry` версии `1`, например так:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -469,7 +475,7 @@ $ pip install "harry==1"
|
||||
|
||||
</div>
|
||||
|
||||
И тогда в вашем глобальном окружении Python будет установлен `harry` версии `1`:
|
||||
Тогда у вас в глобальном окружении Python будет установлен `harry` версии `1`:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
@@ -481,7 +487,7 @@ flowchart LR
|
||||
end
|
||||
```
|
||||
|
||||
Но если позднее вы захотите запустить `prisoner-of-azkaban`, то вам нужно будет удалить `harry` версии 1, и установить `harry` версии `3` (при установке пакета версии `3` поверх пакета версии `1`, пакет версии `1` удаляется автоматически).
|
||||
Но если затем вы захотите запустить `prisoner-of-azkaban`, вам нужно будет удалить `harry` версии `1` и установить `harry` версии `3` (или просто установка версии `3` автоматически удалит версию `1`).
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -491,9 +497,9 @@ $ pip install "harry==3"
|
||||
|
||||
</div>
|
||||
|
||||
И тогда, в вашей глобальной среде окружения Python, будет установлен пакет `harry` версии `3`.
|
||||
В итоге у вас будет установлен `harry` версии `3` в глобальном окружении Python.
|
||||
|
||||
И когда вы снова попытаетесь запустить `philosophers-stone`, то существует вероятность того, что он не будет работать, так как он использует `harry` версии `1`.
|
||||
А если вы снова попробуете запустить `philosophers-stone`, есть шанс, что он **не будет работать**, так как ему нужен `harry` версии `1`.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
@@ -512,47 +518,47 @@ flowchart LR
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
В пакетах Python очень часто стараются изо всех сил избегать внесения критических изменений в новые версии, но лучше перестраховаться и планово устанавливать новые версии, а затем запускать тесты, чтобы проверить, все ли работает правильно.
|
||||
В Python-пакетах часто стараются изо всех сил **избегать ломающих изменений** в **новых версиях**, но лучше действовать осторожно: устанавливать новые версии осознанно и тогда, когда вы можете прогнать тесты и убедиться, что всё работает корректно.
|
||||
|
||||
///
|
||||
|
||||
Теперь представьте, что это происходит со многими другими пакетами, которые используются в ваших проектах. С этим очень сложно справиться. И скорее всего, в конечном итоге вы будете запускать некоторые проекты с некоторыми несовместимыми зависимостями и не будете знать, почему что-то не работает.
|
||||
Теперь представьте то же самое с **многими** другими **пакетами**, от которых зависят все ваши **проекты**. Этим очень сложно управлять. И вы, скорее всего, в какой‑то момент будете запускать проекты с **несовместимыми версиями** пакетов и не понимать, почему что‑то не работает.
|
||||
|
||||
Кроме того, в зависимости от вашей операционной системы (напр. Linux, Windows, macOS), она может поставляться с уже установленным Python. Вероятно, что в этом случае в ней уже установлены системные пакеты определенных версий. Если вы устанавливаете пакеты глобально, то вы можете **поломать** программы, являющиеся частью ОС.
|
||||
Кроме того, в зависимости от ОС (например, Linux, Windows, macOS), она может поставляться с уже установленным Python. И тогда, вероятно, в системе уже есть предустановленные пакеты определённых версий, **нужные вашей системе**. Если вы устанавливаете пакеты в глобальное окружение Python, вы можете в итоге **сломать** некоторые системные программы.
|
||||
|
||||
## Куда устанавливаются пакеты?
|
||||
## Куда устанавливаются пакеты { #where-are-packages-installed }
|
||||
|
||||
Когда вы устанавливаете Python, то на вашей машине создается некоторое количество директорий, содержащих некоторое количество файлов.
|
||||
Когда вы устанавливаете Python, на вашем компьютере создаются некоторые директории с файлами.
|
||||
|
||||
Среди них есть каталоги, отвечающие за хранение всех устанавливаемых вами пакетов.
|
||||
Часть этих директорий отвечает за хранение всех устанавливаемых вами пакетов.
|
||||
|
||||
Когда вы запустите команду:
|
||||
Когда вы запускаете:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
// Не запускайте эту команду, это просто пример 🤓
|
||||
// Не запускайте это сейчас, это просто пример 🤓
|
||||
$ pip install "fastapi[standard]"
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
То будет скачан сжатый файл, содержащий код FastAPI, обычно скачивание происходит с <a href="https://pypi.org/project/fastapi/" class="external-link" target="_blank">PyPI</a>.
|
||||
Будет загружен сжатый файл с кодом FastAPI, обычно с <a href="https://pypi.org/project/fastapi/" class="external-link" target="_blank">PyPI</a>.
|
||||
|
||||
Также будут скачаны файлы, содержащие пакеты, которые использует FastAPI.
|
||||
Также будут **загружены** файлы для других пакетов, от которых зависит FastAPI.
|
||||
|
||||
Затем все файлы будут извлечены и помещены в директорию на вашем компьютере.
|
||||
Затем все эти файлы будут **распакованы** и помещены в директорию на вашем компьютере.
|
||||
|
||||
По умолчанию эти файлы будут загружены и извлечены в один из каталогов установки Python, т.е. в глобальную среду.
|
||||
По умолчанию они попадут в директорию из вашей установки Python — это **глобальное окружение**.
|
||||
|
||||
## Что такое виртуальная среда разработки?
|
||||
## Что такое виртуальные окружения { #what-are-virtual-environments }
|
||||
|
||||
Решением проблемы размещения всех пакетов в глобальной среде будет использование отдельной виртуальной среды под каждый проект, над которым вы работаете.
|
||||
Решение проблемы с пакетами в глобальном окружении — использовать **виртуальное окружение для каждого проекта**, над которым вы работаете.
|
||||
|
||||
Виртуальная среда это обычная папка, очень похожая на глобальную, куда вы можете устанавливать пакеты для вашего проекта.
|
||||
Виртуальное окружение — это **директория**, очень похожая на глобальную, куда вы можете устанавливать пакеты для конкретного проекта.
|
||||
|
||||
Таким образом, каждый проект будет иметь свою отдельную виртуальную среду разработки (в директории `.venv`) вместе со своими пакетами.
|
||||
Таким образом, у каждого проекта будет своё виртуальное окружение (директория `.venv`) со своими пакетами.
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
@@ -571,9 +577,9 @@ flowchart TB
|
||||
stone-project ~~~ azkaban-project
|
||||
```
|
||||
|
||||
## Что означает активация виртуальной среды?
|
||||
## Что означает активация виртуального окружения { #what-does-activating-a-virtual-environment-mean }
|
||||
|
||||
Когда вы активируете виртуальную среду разработки, например, так:
|
||||
Когда вы активируете виртуальное окружение, например так:
|
||||
|
||||
//// tab | Linux, macOS
|
||||
|
||||
@@ -601,7 +607,7 @@ $ .venv\Scripts\Activate.ps1
|
||||
|
||||
//// tab | Windows Bash
|
||||
|
||||
Или если вы воспользуетесь Bash под Windows (напр. <a href="https://gitforwindows.org/" class="external-link" target="_blank">Git Bash</a>):
|
||||
Или если вы используете Bash для Windows (например, <a href="https://gitforwindows.org/" class="external-link" target="_blank">Git Bash</a>):
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -613,10 +619,9 @@ $ source .venv/Scripts/activate
|
||||
|
||||
////
|
||||
|
||||
Эта команда создаст или изменит некоторые [переменные окружения](environment-variables.md){.internal-link target=_blank}, которые будут доступны для следующих команд.
|
||||
|
||||
Эта команда создаст или изменит некоторые [переменные окружения](environment-variables.md){.internal-link target=_blank}, которые будут доступны для последующих команд.
|
||||
|
||||
Одной из таких переменных является `PATH`.
|
||||
Одна из таких переменных — `PATH`.
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
@@ -624,9 +629,9 @@ $ source .venv/Scripts/activate
|
||||
|
||||
///
|
||||
|
||||
При активации виртуальной среды путь `.venv/bin` (для Linux и macOS) или `.venv\Scripts` (для Windows) добавляется в переменную окружения `PATH`.
|
||||
Активация виртуального окружения добавляет его путь `.venv/bin` (на Linux и macOS) или `.venv\Scripts` (на Windows) в переменную окружения `PATH`.
|
||||
|
||||
Предположим, что до активации виртуальной среды переменная `PATH` выглядела так:
|
||||
Предположим, что до активации окружения переменная `PATH` выглядела так:
|
||||
|
||||
//// tab | Linux, macOS
|
||||
|
||||
@@ -634,7 +639,7 @@ $ source .venv/Scripts/activate
|
||||
/usr/bin:/bin:/usr/sbin:/sbin
|
||||
```
|
||||
|
||||
Это означает, что система ищет программы в следующих каталогах:
|
||||
Это означает, что система будет искать программы в:
|
||||
|
||||
* `/usr/bin`
|
||||
* `/bin`
|
||||
@@ -649,13 +654,13 @@ $ source .venv/Scripts/activate
|
||||
C:\Windows\System32
|
||||
```
|
||||
|
||||
Это означает, что система ищет программы в:
|
||||
Это означает, что система будет искать программы в:
|
||||
|
||||
* `C:\Windows\System32`
|
||||
|
||||
////
|
||||
|
||||
После активации виртуальной среды переменная окружение `PATH` будет выглядеть примерно так:
|
||||
После активации виртуального окружения переменная `PATH` будет выглядеть примерно так:
|
||||
|
||||
//// tab | Linux, macOS
|
||||
|
||||
@@ -663,21 +668,21 @@ C:\Windows\System32
|
||||
/home/user/code/awesome-project/.venv/bin:/usr/bin:/bin:/usr/sbin:/sbin
|
||||
```
|
||||
|
||||
Это означает, что система теперь будет искать программы в:
|
||||
Это означает, что теперь система в первую очередь будет искать программы в:
|
||||
|
||||
```plaintext
|
||||
/home/user/code/awesome-project/.venv/bin
|
||||
```
|
||||
|
||||
прежде чем начать искать в других каталогах.
|
||||
прежде чем искать в других директориях.
|
||||
|
||||
Таким образом, когда вы введете в консоль `python`, система будет искать Python в
|
||||
Поэтому, когда вы введёте в терминале `python`, система найдёт программу Python по пути
|
||||
|
||||
```plaintext
|
||||
/home/user/code/awesome-project/.venv/bin/python
|
||||
```
|
||||
|
||||
и будет использовать именно его.
|
||||
и использует именно её.
|
||||
|
||||
////
|
||||
|
||||
@@ -687,31 +692,31 @@ C:\Windows\System32
|
||||
C:\Users\user\code\awesome-project\.venv\Scripts;C:\Windows\System32
|
||||
```
|
||||
|
||||
Это означает, что система в первую очередь начнет искать программы в:
|
||||
Это означает, что теперь система в первую очередь будет искать программы в:
|
||||
|
||||
```plaintext
|
||||
C:\Users\user\code\awesome-project\.venv\Scripts
|
||||
```
|
||||
|
||||
прежде чем начать искать в других директориях.
|
||||
прежде чем искать в других директориях.
|
||||
|
||||
Таким образом, если вы введете в консоль команду `python`, то система найдет Python в:
|
||||
Поэтому, когда вы введёте в терминале `python`, система найдёт программу Python по пути
|
||||
|
||||
```plaintext
|
||||
C:\Users\user\code\awesome-project\.venv\Scripts\python
|
||||
```
|
||||
|
||||
и использует его.
|
||||
и использует именно её.
|
||||
|
||||
////
|
||||
|
||||
Очень важной деталью является то, что путь к виртуальной среде будет помещен в самое начало переменной `PATH`. Система обнаружит данный путь к Python раньше, чем какой-либо другой. Таким образом, при запуске команды `python`, будет использован именно Python из виртуальной среды разработки, а не какой-нибудь другой (например, Python из глобальной среды)
|
||||
Важная деталь: путь к виртуальному окружению будет добавлен в самое **начало** переменной `PATH`. Система найдёт его **раньше**, чем любой другой установленный Python. Таким образом, при запуске `python` будет использоваться Python **из виртуального окружения**, а не какой‑то другой `python` (например, из глобального окружения).
|
||||
|
||||
Активация виртуальной среды разработки также меняет и несколько других вещей, но данная функция является основной.
|
||||
Активация виртуального окружения также меняет ещё несколько вещей, но это — одна из важнейших.
|
||||
|
||||
## Проверка виртуальной среды
|
||||
## Проверка виртуального окружения { #checking-a-virtual-environment }
|
||||
|
||||
Когда вы проверяете активна ли виртуальная среда разработки, например, так:
|
||||
Когда вы проверяете, активно ли виртуальное окружение, например, так:
|
||||
|
||||
//// tab | Linux, macOS, Windows Bash
|
||||
|
||||
@@ -741,33 +746,33 @@ C:\Users\user\code\awesome-project\.venv\Scripts\python
|
||||
|
||||
////
|
||||
|
||||
Это означает, что будет использоваться `python` **из виртуальной среды разработки**.
|
||||
Это означает, что будет использоваться программа `python` **из виртуального окружения**.
|
||||
|
||||
Вы используете `which` для Linux и macOS и `Get-Command` для Windows PowerShell.
|
||||
На Linux и macOS используется `which`, а в Windows PowerShell — `Get-Command`.
|
||||
|
||||
Эта команда работает следующим образом: она проверяет переменную окружения `PATH`, проходя по очереди каждый указанный путь в поисках программы под названием `python`. И когда она её находит, то возвращает путь к данной программе.
|
||||
Как работает эта команда: она проходит по переменной окружения `PATH`, идя **по каждому пути по порядку**, и ищет программу с именем `python`. Как только находит — **показывает путь** к этой программе.
|
||||
|
||||
Основной момент при вызове команды `python` состоит в том, какой именно "`python`" будет запущен.
|
||||
Самое важное — при вызове `python` именно этот «`python`» и будет выполняться.
|
||||
|
||||
Таким образом, вы можете убедиться, что используете правильную виртуальную среду разработки.
|
||||
Так вы можете подтвердить, что находитесь в правильном виртуальном окружении.
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Легко активировать одну виртуальную среду, вызвать один Python и **перейти к следующему проекту**.
|
||||
Легко активировать одно виртуальное окружение, получить один Python, а затем **перейти к другому проекту**.
|
||||
|
||||
И следующий проект не будет работать потому, что вы используете **неправильный Python** из виртуальной среды другого проекта.
|
||||
И второй проект **не будет работать**, потому что вы используете **не тот Python**, из виртуального окружения другого проекта.
|
||||
|
||||
Так что, будет нелишним проверить, какой `python` вы используете. 🤓
|
||||
Полезно уметь проверить, какой именно `python` используется. 🤓
|
||||
|
||||
///
|
||||
|
||||
## Зачем деактивируют виртуальную среду?
|
||||
## Зачем деактивировать виртуальное окружение { #why-deactivate-a-virtual-environment }
|
||||
|
||||
Предположим, что вы работаете над проектом `philosophers-stone`, **активируете виртуальную среду разработки**, устанавливаете пакеты и работаете с данной средой.
|
||||
Например, вы работаете над проектом `philosophers-stone`, **активируете виртуальное окружение**, устанавливаете пакеты и работаете с ним.
|
||||
|
||||
И позже вам понадобилось поработать с **другим проектом** `prisoner-of-azkaban`.
|
||||
Затем вы хотите поработать над **другим проектом** `prisoner-of-azkaban`.
|
||||
|
||||
Вы переходите к этому проекту:
|
||||
Вы переходите в этот проект:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -777,7 +782,7 @@ $ cd ~/code/prisoner-of-azkaban
|
||||
|
||||
</div>
|
||||
|
||||
Если вы не деактивировали виртуальное окружение проекта `philosophers-stone`, то при запуске `python` через консоль будет вызван Python из `philosophers-stone`
|
||||
Если вы не деактивируете виртуальное окружение `philosophers-stone`, при запуске `python` в терминале он попытается использовать Python из `philosophers-stone`.
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -794,46 +799,46 @@ Traceback (most recent call last):
|
||||
|
||||
</div>
|
||||
|
||||
Но если вы деактивируете виртуальную среду разработки и активируете новую среду для `prisoner-of-askaban`, то вы тогда запустите Python из виртуального окружения `prisoner-of-azkaban`.
|
||||
Но если вы деактивируете виртуальное окружение и активируете новое для `prisoner-of-askaban`, тогда при запуске `python` он будет использовать Python из виртуального окружения `prisoner-of-azkaban`.
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ cd ~/code/prisoner-of-azkaban
|
||||
|
||||
// Вам не требуется находится в старой директории для деактивации среды разработки, вы можете это сделать откуда угодно, даже из каталога другого проекта, в который вы перешли. 😎
|
||||
// Вам не нужно находиться в старой директории, чтобы деактивировать окружение, вы можете сделать это где угодно, даже после перехода в другой проект 😎
|
||||
$ deactivate
|
||||
|
||||
// Активируйте виртуальную среду разработки в prisoner-of-azkaban/.venv 🚀
|
||||
// Активируйте виртуальное окружение в prisoner-of-azkaban/.venv 🚀
|
||||
$ source .venv/bin/activate
|
||||
|
||||
// Тепреь, когда вы запустите python, он найдет пакет sirius, установленный в виртуальной среде ✨
|
||||
// Теперь при запуске python он найдёт пакет sirius, установленный в этом виртуальном окружении ✨
|
||||
$ python main.py
|
||||
|
||||
Я торжественно клянусь в этом! 🐺
|
||||
I solemnly swear 🐺
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## Альтернативы
|
||||
## Альтернативы { #alternatives }
|
||||
|
||||
Это простое руководство поможет вам начать работу и научит тому, как всё работает **изнутри**.
|
||||
Это простое руководство, чтобы вы начали и поняли, как всё работает **под капотом**.
|
||||
|
||||
Существует много альтернативных решений для работы с виртуальными средами разработки, с программными зависимостями, а также с проектами.
|
||||
Существует много **альтернатив** для управления виртуальными окружениями, зависимостями (requirements), проектами.
|
||||
|
||||
Когда вы будете готовы использовать единый инструмент для управления проектом, программными зависимостями, виртуальными средами разработки и т.д., то я рекомендую вам попробовать <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">uv</a>.
|
||||
Когда вы будете готовы и захотите использовать инструмент для **управления всем проектом** — зависимостями пакетов, виртуальными окружениями и т. п., я бы предложил попробовать <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">uv</a>.
|
||||
|
||||
`uv` может очень многое. Он умеет:
|
||||
`uv` может многое:
|
||||
|
||||
* **Устанавливать Python**, включая установку различных версий
|
||||
* Управлять средой виртуального окружения вашего проекта
|
||||
* **Устанавливать Python**, включая разные версии
|
||||
* Управлять **виртуальным окружением** ваших проектов
|
||||
* Устанавливать **пакеты**
|
||||
* Управлять пакетами и их версиями внутри вашего проекта
|
||||
* Удостовериться, что вы используете **точный** набор пакетов и версий при установке, включая зависимости. Таким образом, вы можете быть уверенны, что проект, запускается в production, точно также, как и при разработке, этот механизм называется *locking*
|
||||
* Многие другие вещи
|
||||
* Управлять **зависимостями и версиями** пакетов вашего проекта
|
||||
* Обеспечивать наличие **точного** набора пакетов и версий к установке, включая их зависимости, чтобы вы были уверены, что сможете запускать проект в продакшне точно так же, как и на компьютере при разработке — это называется **locking**
|
||||
* И многое другое
|
||||
|
||||
## Заключение
|
||||
## Заключение { #conclusion }
|
||||
|
||||
Если вы прочитали и поняли всё это, то теперь вы знаете **гораздо больше** о виртуальных средах разработки, чем многие другие разработчики. 🤓
|
||||
Если вы прочитали и поняли всё это, теперь **вы знаете гораздо больше** о виртуальных окружениях, чем многие разработчики. 🤓
|
||||
|
||||
Скорее всего, знание этих деталей будет полезно вам в будущем. Когда вы будете отлаживать что-то, кажущееся сложным, вы будете знать, **как это работает под капотом**. 😎
|
||||
Знание этих деталей, скорее всего, пригодится вам в будущем, когда вы будете отлаживать что‑то сложное: вы будете понимать, **как всё работает под капотом**. 😎
|
||||
|
||||
Reference in New Issue
Block a user