mirror of https://github.com/tiangolo/fastapi.git
Merge branch 'master' into fix-aliases
This commit is contained in:
commit
bf5be37363
|
|
@ -7,6 +7,12 @@ hide:
|
|||
|
||||
## Latest Changes
|
||||
|
||||
## 0.124.3
|
||||
|
||||
### Fixes
|
||||
|
||||
* 🐛 Fix support for tagged union with discriminator inside of `Annotated` with `Body()`. PR [#14512](https://github.com/fastapi/fastapi/pull/14512) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
### Refactors
|
||||
|
||||
* ✅ Add set of tests for request parameters and alias. PR [#14358](https://github.com/fastapi/fastapi/pull/14358) by [@YuriiMotov](https://github.com/YuriiMotov).
|
||||
|
|
@ -19,6 +25,7 @@ hide:
|
|||
|
||||
### Translations
|
||||
|
||||
* 🌐 Sync Russian docs. PR [#14509](https://github.com/fastapi/fastapi/pull/14509) by [@YuriiMotov](https://github.com/YuriiMotov).
|
||||
* 🌐 Sync German docs. PR [#14488](https://github.com/fastapi/fastapi/pull/14488) by [@nilslindemann](https://github.com/nilslindemann).
|
||||
|
||||
### Internal
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
Тесты:
|
||||
|
||||
## Фрагменты кода { #code-snippets}
|
||||
## Фрагменты кода { #code-snippets }
|
||||
|
||||
//// tab | Тест
|
||||
|
||||
|
|
@ -53,7 +53,7 @@ LLM, вероятно, переведёт это неправильно. Инт
|
|||
|
||||
////
|
||||
|
||||
## Кавычки во фрагментах кода { #quotes-in-code-snippets}
|
||||
## Кавычки во фрагментах кода { #quotes-in-code-snippets }
|
||||
|
||||
//// tab | Тест
|
||||
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@
|
|||
|
||||
Например, вы можете добавить дополнительный тип содержимого `image/png`, объявив, что ваша операция пути может возвращать JSON‑объект (с типом содержимого `application/json`) или PNG‑изображение:
|
||||
|
||||
{* ../../docs_src/additional_responses/tutorial002.py hl[19:24,28] *}
|
||||
{* ../../docs_src/additional_responses/tutorial002_py310.py hl[17:22,26] *}
|
||||
|
||||
/// note | Примечание
|
||||
|
||||
|
|
@ -237,7 +237,7 @@ new_dict = {**old_dict, "new key": "new value"}
|
|||
|
||||
Например:
|
||||
|
||||
{* ../../docs_src/additional_responses/tutorial004.py hl[13:17,26] *}
|
||||
{* ../../docs_src/additional_responses/tutorial004_py310.py hl[11:15,24] *}
|
||||
|
||||
## Дополнительная информация об ответах OpenAPI { #more-information-about-openapi-responses }
|
||||
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ checker(q="somequery")
|
|||
|
||||
### Фоновые задачи и зависимости с `yield`, технические детали { #background-tasks-and-dependencies-with-yield-technical-details }
|
||||
|
||||
До FastAPI 0.106.0 вызывать исключения после `yield` было невозможно: код после `yield` в зависимостях выполнялся уже после отправки ответа, поэтому [Обработчики исключений](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} к тому моменту уже отработали.
|
||||
До FastAPI 0.106.0 вызывать исключения после `yield` было невозможно: код после `yield` в зависимостях выполнялся уже после отправки ответа, поэтому [Обработчики исключений](../tutorial/handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} к тому моменту уже отработали.
|
||||
|
||||
Так было сделано в основном для того, чтобы можно было использовать те же объекты, «отданные» зависимостями через `yield`, внутри фоновых задач, потому что код после `yield` выполнялся после завершения фоновых задач.
|
||||
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ https://mysuperapp.com/items/
|
|||
|
||||
///
|
||||
|
||||
### Как работают пересылаемые заголовки прокси
|
||||
### Как работают пересылаемые заголовки прокси { #how-proxy-forwarded-headers-work }
|
||||
|
||||
Ниже показано, как прокси добавляет пересылаемые заголовки между клиентом и сервером приложения:
|
||||
|
||||
|
|
@ -443,6 +443,14 @@ $ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1
|
|||
|
||||
///
|
||||
|
||||
/// note | Технические детали
|
||||
|
||||
Свойство `servers` в спецификации OpenAPI является необязательным.
|
||||
|
||||
Если вы не укажете параметр `servers`, а `root_path` равен `/`, то свойство `servers` в сгенерированной схеме OpenAPI по умолчанию будет опущено. Это эквивалентно серверу со значением `url` равным `/`.
|
||||
|
||||
///
|
||||
|
||||
### Отключить автоматическое добавление сервера из `root_path` { #disable-automatic-server-from-root-path }
|
||||
|
||||
Если вы не хотите, чтобы FastAPI добавлял автоматический сервер, используя `root_path`, укажите параметр `root_path_in_servers=False`:
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ FastAPI построен поверх **Pydantic**, и я показывал в
|
|||
|
||||
Но FastAPI также поддерживает использование <a href="https://docs.python.org/3/library/dataclasses.html" class="external-link" target="_blank">`dataclasses`</a> тем же способом:
|
||||
|
||||
{* ../../docs_src/dataclasses/tutorial001.py hl[1,7:12,19:20] *}
|
||||
{* ../../docs_src/dataclasses/tutorial001_py310.py hl[1,6:11,18:19] *}
|
||||
|
||||
Это по-прежнему поддерживается благодаря **Pydantic**, так как в нём есть <a href="https://docs.pydantic.dev/latest/concepts/dataclasses/#use-of-stdlib-dataclasses-with-basemodel" class="external-link" target="_blank">встроенная поддержка `dataclasses`</a>.
|
||||
|
||||
|
|
@ -32,7 +32,7 @@ FastAPI построен поверх **Pydantic**, и я показывал в
|
|||
|
||||
Вы также можете использовать `dataclasses` в параметре `response_model`:
|
||||
|
||||
{* ../../docs_src/dataclasses/tutorial002.py hl[1,7:13,19] *}
|
||||
{* ../../docs_src/dataclasses/tutorial002_py310.py hl[1,6:12,18] *}
|
||||
|
||||
Этот dataclass будет автоматически преобразован в Pydantic dataclass.
|
||||
|
||||
|
|
@ -48,7 +48,7 @@ FastAPI построен поверх **Pydantic**, и я показывал в
|
|||
|
||||
В таком случае вы можете просто заменить стандартные `dataclasses` на `pydantic.dataclasses`, которая является полностью совместимой заменой (drop-in replacement):
|
||||
|
||||
{* ../../docs_src/dataclasses/tutorial003.py hl[1,5,8:11,14:17,23:25,28] *}
|
||||
{* ../../docs_src/dataclasses/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *}
|
||||
|
||||
1. Мы по-прежнему импортируем `field` из стандартных `dataclasses`.
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
|
||||
Эта часть вполне обычна, большая часть кода вам уже знакома:
|
||||
|
||||
{* ../../docs_src/openapi_callbacks/tutorial001.py hl[9:13,36:53] *}
|
||||
{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[7:11,34:51] *}
|
||||
|
||||
/// tip | Совет
|
||||
|
||||
|
|
@ -90,7 +90,7 @@ httpx.post(callback_url, json={"description": "Invoice paid", "paid": True})
|
|||
|
||||
Сначала создайте новый `APIRouter`, который будет содержать один или несколько обратных вызовов.
|
||||
|
||||
{* ../../docs_src/openapi_callbacks/tutorial001.py hl[3,25] *}
|
||||
{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[1,23] *}
|
||||
|
||||
### Создайте *операцию пути* для обратного вызова { #create-the-callback-path-operation }
|
||||
|
||||
|
|
@ -101,7 +101,7 @@ httpx.post(callback_url, json={"description": "Invoice paid", "paid": True})
|
|||
* Вероятно, в ней должно быть объявление тела запроса, например `body: InvoiceEvent`.
|
||||
* А также может быть объявление модели ответа, например `response_model=InvoiceEventReceived`.
|
||||
|
||||
{* ../../docs_src/openapi_callbacks/tutorial001.py hl[16:18,21:22,28:32] *}
|
||||
{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[14:16,19:20,26:30] *}
|
||||
|
||||
Есть 2 основных отличия от обычной *операции пути*:
|
||||
|
||||
|
|
@ -169,7 +169,7 @@ https://www.external.org/events/invoices/2expen51ve
|
|||
|
||||
Теперь используйте параметр `callbacks` в *декораторе операции пути вашего API*, чтобы передать атрибут `.routes` (это, по сути, просто `list` маршрутов/*операций пути*) из этого маршрутизатора обратных вызовов:
|
||||
|
||||
{* ../../docs_src/openapi_callbacks/tutorial001.py hl[35] *}
|
||||
{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[33] *}
|
||||
|
||||
/// tip | Совет
|
||||
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@
|
|||
|
||||
Эта часть не попадёт в документацию, но другие инструменты (например, Sphinx) смогут использовать остальное.
|
||||
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial004.py hl[19:29] *}
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial004_py310.py hl[17:27] *}
|
||||
|
||||
## Дополнительные ответы { #additional-responses }
|
||||
|
||||
|
|
@ -155,13 +155,13 @@
|
|||
|
||||
//// tab | Pydantic v2
|
||||
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[17:22, 24] *}
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[15:20, 22] *}
|
||||
|
||||
////
|
||||
|
||||
//// tab | Pydantic v1
|
||||
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py hl[17:22, 24] *}
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py hl[15:20, 22] *}
|
||||
|
||||
////
|
||||
|
||||
|
|
@ -179,13 +179,13 @@
|
|||
|
||||
//// tab | Pydantic v2
|
||||
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[26:33] *}
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[24:31] *}
|
||||
|
||||
////
|
||||
|
||||
//// tab | Pydantic v1
|
||||
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py hl[26:33] *}
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py hl[24:31] *}
|
||||
|
||||
////
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@
|
|||
|
||||
В таких случаях вы можете использовать `jsonable_encoder` для преобразования данных перед передачей их в ответ:
|
||||
|
||||
{* ../../docs_src/response_directly/tutorial001.py hl[6:7,21:22] *}
|
||||
{* ../../docs_src/response_directly/tutorial001_py310.py hl[5:6,20:21] *}
|
||||
|
||||
/// note | Технические детали
|
||||
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ $ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.p
|
|||
|
||||
Продолжая предыдущий пример, ваш файл `config.py` может выглядеть так:
|
||||
|
||||
{* ../../docs_src/settings/app02/config.py hl[10] *}
|
||||
{* ../../docs_src/settings/app02_an_py39/config.py hl[10] *}
|
||||
|
||||
Обратите внимание, что теперь мы не создаем экземпляр по умолчанию `settings = Settings()`.
|
||||
|
||||
|
|
@ -174,7 +174,7 @@ $ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.p
|
|||
|
||||
Далее будет очень просто предоставить другой объект настроек во время тестирования, создав переопределение зависимости для `get_settings`:
|
||||
|
||||
{* ../../docs_src/settings/app02/test_main.py hl[9:10,13,21] *}
|
||||
{* ../../docs_src/settings/app02_an_py39/test_main.py hl[9:10,13,21] *}
|
||||
|
||||
В переопределении зависимости мы задаем новое значение `admin_email` при создании нового объекта `Settings`, а затем возвращаем этот новый объект.
|
||||
|
||||
|
|
@ -217,7 +217,7 @@ APP_NAME="ChimichangApp"
|
|||
|
||||
//// tab | Pydantic v2
|
||||
|
||||
{* ../../docs_src/settings/app03_an/config.py hl[9] *}
|
||||
{* ../../docs_src/settings/app03_an_py39/config.py hl[9] *}
|
||||
|
||||
/// tip | Совет
|
||||
|
||||
|
|
@ -229,7 +229,7 @@ APP_NAME="ChimichangApp"
|
|||
|
||||
//// tab | Pydantic v1
|
||||
|
||||
{* ../../docs_src/settings/app03_an/config_pv1.py hl[9:10] *}
|
||||
{* ../../docs_src/settings/app03_an_py39/config_pv1.py hl[9:10] *}
|
||||
|
||||
/// tip | Совет
|
||||
|
||||
|
|
|
|||
|
|
@ -4,11 +4,19 @@
|
|||
|
||||
В большинстве случаев у основных облачных провайдеров есть руководства по развертыванию FastAPI на их платформе.
|
||||
|
||||
## FastAPI Cloud { #fastapi-cloud }
|
||||
|
||||
**<a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>** создан тем же автором и командой, стоящими за **FastAPI**.
|
||||
|
||||
Он упрощает процесс **создания образа**, **развертывания** и **доступа** к API с минимальными усилиями.
|
||||
|
||||
Он переносит тот же **опыт разработчика** создания приложений с FastAPI на их **развертывание** в облаке. 🎉
|
||||
|
||||
FastAPI Cloud — основной спонсор и источник финансирования для open source проектов *FastAPI and friends*. ✨
|
||||
|
||||
## Облачные провайдеры — спонсоры { #cloud-providers-sponsors }
|
||||
|
||||
Некоторые облачные провайдеры ✨ [**спонсируют FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨ — это обеспечивает непрерывное и здоровое развитие FastAPI и его экосистемы.
|
||||
|
||||
И это показывает их искреннюю приверженность FastAPI и его сообществу (вам): они не только хотят предоставить вам хороший сервис, но и стремятся гарантировать, что у вас будет хороший и стабильный фреймворк — FastAPI. 🙇
|
||||
Некоторые другие облачные провайдеры ✨ [**спонсируют FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨ тоже. 🙇
|
||||
|
||||
Возможно, вы захотите попробовать их сервисы и воспользоваться их руководствами:
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
# FastAPI Cloud { #fastapi-cloud }
|
||||
|
||||
Вы можете развернуть своё приложение FastAPI в <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a> одной командой, присоединяйтесь к списку ожидания, если ещё не сделали этого. 🚀
|
||||
|
||||
## Вход { #login }
|
||||
|
||||
Убедитесь, что у вас уже есть аккаунт **FastAPI Cloud** (мы пригласили вас из списка ожидания 😉).
|
||||
|
||||
Затем выполните вход:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ fastapi login
|
||||
|
||||
You are logged in to FastAPI Cloud 🚀
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## Деплой { #deploy }
|
||||
|
||||
Теперь разверните приложение одной командой:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ fastapi deploy
|
||||
|
||||
Deploying to FastAPI Cloud...
|
||||
|
||||
✅ Deployment successful!
|
||||
|
||||
🐔 Ready the chicken! Your app is ready at https://myapp.fastapicloud.dev
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
Вот и всё! Теперь вы можете открыть своё приложение по этому URL. ✨
|
||||
|
||||
## О FastAPI Cloud { #about-fastapi-cloud }
|
||||
|
||||
**<a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>** создан тем же автором и командой, что и **FastAPI**.
|
||||
|
||||
Он упрощает процесс **создания образа**, **развертывания** и **доступа** к API с минимальными усилиями.
|
||||
|
||||
Он переносит тот же **опыт разработчика**, что вы получаете при создании приложений на FastAPI, на их **развертывание** в облаке. 🎉
|
||||
|
||||
Он также возьмёт на себя большинство вещей, которые требуются при развертывании приложения, например:
|
||||
|
||||
* HTTPS
|
||||
* Репликация с автоматическим масштабированием на основе запросов
|
||||
* и т.д.
|
||||
|
||||
FastAPI Cloud — основной спонсор и источник финансирования open source‑проектов «FastAPI и друзья». ✨
|
||||
|
||||
## Развертывание у других облачных провайдеров { #deploy-to-other-cloud-providers }
|
||||
|
||||
FastAPI — проект с открытым исходным кодом и основан на стандартах. Вы можете развернуть приложения FastAPI у любого облачного провайдера на ваш выбор.
|
||||
|
||||
Следуйте руководствам вашего облачного провайдера, чтобы развернуть приложения FastAPI у них. 🤓
|
||||
|
||||
## Развертывание на собственном сервере { #deploy-your-own-server }
|
||||
|
||||
Позже в этом руководстве по **развертыванию** я также расскажу все детали — чтобы вы понимали, что происходит, что нужно сделать и как развернуть приложения FastAPI самостоятельно, в том числе на собственных серверах. 🤓
|
||||
|
|
@ -12,10 +12,12 @@
|
|||
|
||||
## Стратегии развёртывания { #deployment-strategies }
|
||||
|
||||
В зависимости от вашего конкретного случая, есть несколько способов сделать это.
|
||||
Есть несколько способов сделать это, в зависимости от вашего конкретного случая и используемых вами инструментов.
|
||||
|
||||
Вы можете **развернуть сервер** самостоятельно, используя различные инструменты. Например, можно использовать **облачный сервис**, который выполнит часть работы за вас. Также возможны и другие варианты.
|
||||
|
||||
Например, мы, команда, стоящая за FastAPI, создали <a href="https://fastapicloud.com" class="external-link" target="_blank">**FastAPI Cloud**</a>, чтобы сделать развёртывание приложений FastAPI в облаке как можно более простым и прямолинейным, с тем же удобством для разработчика, что и при работе с FastAPI.
|
||||
|
||||
В этом блоке я покажу вам некоторые из основных концепций, которые вы, вероятно, должны иметь в виду при развертывании приложения **FastAPI** (хотя большинство из них применимо к любому другому типу веб-приложений).
|
||||
|
||||
В последующих разделах вы узнаете больше деталей и методов, необходимых для этого. ✨
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
# Использование старых статус-кодов ошибок аутентификации 403 { #use-old-403-authentication-error-status-codes }
|
||||
|
||||
До версии FastAPI `0.122.0`, когда встроенные утилиты безопасности возвращали ошибку клиенту после неудачной аутентификации, они использовали HTTP статус-код `403 Forbidden`.
|
||||
|
||||
Начиная с версии FastAPI `0.122.0`, используется более подходящий HTTP статус-код `401 Unauthorized`, и в ответе возвращается имеющий смысл HTTP-заголовок `WWW-Authenticate` в соответствии со спецификациями HTTP, <a href="https://datatracker.ietf.org/doc/html/rfc7235#section-3.1" class="external-link" target="_blank">RFC 7235</a>, <a href="https://datatracker.ietf.org/doc/html/rfc9110#name-401-unauthorized" class="external-link" target="_blank">RFC 9110</a>.
|
||||
|
||||
Но если по какой-то причине ваши клиенты зависят от старого поведения, вы можете вернуть его, переопределив метод `make_not_authenticated_error` в ваших Security-классах.
|
||||
|
||||
Например, вы можете создать подкласс `HTTPBearer`, который будет возвращать ошибку `403 Forbidden` вместо стандартной `401 Unauthorized`:
|
||||
|
||||
{* ../../docs_src/authentication_error_status_code/tutorial001_an_py39.py hl[9:13] *}
|
||||
|
||||
/// tip | Совет
|
||||
|
||||
Обратите внимание, что функция возвращает экземпляр исключения, не вызывает его. Выброс выполняется остальным внутренним кодом.
|
||||
|
||||
///
|
||||
|
|
@ -40,7 +40,7 @@ FastAPI включает некоторые параметры конфигур
|
|||
|
||||
Это включает следующие настройки по умолчанию:
|
||||
|
||||
{* ../../fastapi/openapi/docs.py ln[8:23] hl[17:23] *}
|
||||
{* ../../fastapi/openapi/docs.py ln[9:24] hl[18:24] *}
|
||||
|
||||
Вы можете переопределить любую из них, указав другое значение в аргументе `swagger_ui_parameters`.
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@
|
|||
|
||||
Таким образом, один и тот же класс маршрута сможет обрабатывать как gzip-сжатые, так и несжатые запросы.
|
||||
|
||||
{* ../../docs_src/custom_request_and_route/tutorial001.py hl[8:15] *}
|
||||
{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[9:16] *}
|
||||
|
||||
### Создать пользовательский класс `GzipRoute` { #create-a-custom-gziproute-class }
|
||||
|
||||
|
|
@ -54,7 +54,7 @@
|
|||
|
||||
Здесь мы используем её, чтобы создать `GzipRequest` из исходного HTTP-запроса.
|
||||
|
||||
{* ../../docs_src/custom_request_and_route/tutorial001.py hl[18:26] *}
|
||||
{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[19:27] *}
|
||||
|
||||
/// note | Технические детали
|
||||
|
||||
|
|
@ -92,18 +92,18 @@
|
|||
|
||||
Нужно лишь обработать запрос внутри блока `try`/`except`:
|
||||
|
||||
{* ../../docs_src/custom_request_and_route/tutorial002.py hl[13,15] *}
|
||||
{* ../../docs_src/custom_request_and_route/tutorial002_an_py310.py hl[14,16] *}
|
||||
|
||||
Если произойдёт исключение, экземпляр `Request` всё ещё будет в области видимости, поэтому мы сможем прочитать тело запроса и использовать его при обработке ошибки:
|
||||
|
||||
{* ../../docs_src/custom_request_and_route/tutorial002.py hl[16:18] *}
|
||||
{* ../../docs_src/custom_request_and_route/tutorial002_an_py310.py hl[17:19] *}
|
||||
|
||||
## Пользовательский класс `APIRoute` в роутере { #custom-apiroute-class-in-a-router }
|
||||
|
||||
Вы также можете задать параметр `route_class` у `APIRouter`:
|
||||
|
||||
{* ../../docs_src/custom_request_and_route/tutorial003.py hl[26] *}
|
||||
{* ../../docs_src/custom_request_and_route/tutorial003_py310.py hl[26] *}
|
||||
|
||||
В этом примере *операции пути*, объявленные в `router`, будут использовать пользовательский класс `TimedRoute` и получат дополнительный HTTP-заголовок `X-Response-Time` в ответе с временем, затраченным на формирование ответа:
|
||||
|
||||
{* ../../docs_src/custom_request_and_route/tutorial003.py hl[13:20] *}
|
||||
{* ../../docs_src/custom_request_and_route/tutorial003_py310.py hl[13:20] *}
|
||||
|
|
|
|||
|
|
@ -52,14 +52,20 @@ FastAPI — это современный, быстрый (высокопрои
|
|||
|
||||
<!-- sponsors -->
|
||||
|
||||
{% if sponsors %}
|
||||
### Ключевой-спонсор { #keystone-sponsor }
|
||||
|
||||
{% for sponsor in sponsors.keystone -%}
|
||||
<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a>
|
||||
{% endfor -%}
|
||||
|
||||
### Золотые и серебряные спонсоры { #gold-and-silver-sponsors }
|
||||
|
||||
{% for sponsor in sponsors.gold -%}
|
||||
<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a>
|
||||
{% endfor -%}
|
||||
{%- for sponsor in sponsors.silver -%}
|
||||
<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<!-- /sponsors -->
|
||||
|
||||
|
|
@ -444,6 +450,58 @@ item: Item
|
|||
* **сессии с использованием cookie**
|
||||
* ...и многое другое.
|
||||
|
||||
### Разверните приложение (опционально) { #deploy-your-app-optional }
|
||||
|
||||
При желании вы можете развернуть своё приложение FastAPI в <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>, присоединяйтесь к списку ожидания, если ещё не сделали этого. 🚀
|
||||
|
||||
Если у вас уже есть аккаунт **FastAPI Cloud** (мы пригласили вас из списка ожидания 😉), вы можете развернуть ваше приложение одной командой.
|
||||
|
||||
Перед развертыванием убедитесь, что вы вошли в систему:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ fastapi login
|
||||
|
||||
You are logged in to FastAPI Cloud 🚀
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
Затем разверните приложение:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ fastapi deploy
|
||||
|
||||
Deploying to FastAPI Cloud...
|
||||
|
||||
✅ Deployment successful!
|
||||
|
||||
🐔 Ready the chicken! Your app is ready at https://myapp.fastapicloud.dev
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
Вот и всё! Теперь вы можете открыть ваше приложение по этой ссылке. ✨
|
||||
|
||||
#### О FastAPI Cloud { #about-fastapi-cloud }
|
||||
|
||||
**<a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>** создан тем же автором и командой, что и **FastAPI**.
|
||||
|
||||
Он упрощает процесс **создания образа**, **развертывания** и **доступа** к API при минимальных усилиях.
|
||||
|
||||
Он переносит тот же **опыт разработчика**, что и при создании приложений на FastAPI, на их **развертывание** в облаке. 🎉
|
||||
|
||||
FastAPI Cloud — основной спонсор и источник финансирования для проектов с открытым исходным кодом из экосистемы *FastAPI and friends*. ✨
|
||||
|
||||
#### Развертывание у других облачных провайдеров { #deploy-to-other-cloud-providers }
|
||||
|
||||
FastAPI — это open source и стандартизированный фреймворк. Вы можете развернуть приложения FastAPI у любого облачного провайдера на ваш выбор.
|
||||
|
||||
Следуйте руководствам вашего облачного провайдера по развертыванию приложений FastAPI. 🤓
|
||||
|
||||
## Производительность { #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). (*)
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@
|
|||
- 🔍 [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) для компонентов фронтенда.
|
||||
- 💃 Используются TypeScript, хуки, Vite и другие части современного фронтенд‑стека.
|
||||
- 🎨 [Tailwind CSS](https://tailwindcss.com) и [shadcn/ui](https://ui.shadcn.com) для компонентов фронтенда.
|
||||
- 🤖 Автоматически сгенерированный фронтенд‑клиент.
|
||||
- 🧪 [Playwright](https://playwright.dev) для End‑to‑End тестирования.
|
||||
- 🦇 Поддержка тёмной темы.
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
# Ресурсы { #resources }
|
||||
|
||||
Дополнительные ресурсы, внешние ссылки, статьи и многое другое. ✈️
|
||||
Дополнительные ресурсы, внешние ссылки и многое другое. ✈️
|
||||
|
|
|
|||
|
|
@ -85,17 +85,13 @@ from app.routers import items
|
|||
|
||||
Точно также, как и в случае с классом `FastAPI`, вам нужно импортировать и создать объект класса `APIRouter`.
|
||||
|
||||
```Python hl_lines="1 3" title="app/routers/users.py"
|
||||
{!../../docs_src/bigger_applications/app/routers/users.py!}
|
||||
```
|
||||
{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[1,3] title["app/routers/users.py"] *}
|
||||
|
||||
### Создание *эндпоинтов* с помощью `APIRouter` { #path-operations-with-apirouter }
|
||||
|
||||
В дальнейшем используйте `APIRouter` для объявления *эндпоинтов*, точно также, как вы используете класс `FastAPI`:
|
||||
|
||||
```Python hl_lines="6 11 16" title="app/routers/users.py"
|
||||
{!../../docs_src/bigger_applications/app/routers/users.py!}
|
||||
```
|
||||
{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[6,11,16] title["app/routers/users.py"] *}
|
||||
|
||||
Вы можете думать об `APIRouter` как об "уменьшенной версии" класса FastAPI`.
|
||||
|
||||
|
|
@ -119,35 +115,7 @@ from app.routers import items
|
|||
|
||||
Теперь мы воспользуемся простой зависимостью, чтобы прочитать кастомизированный `X-Token` из заголовка:
|
||||
|
||||
//// tab | Python 3.9+
|
||||
|
||||
```Python hl_lines="3 6-8" title="app/dependencies.py"
|
||||
{!> ../../docs_src/bigger_applications/app_an_py39/dependencies.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+
|
||||
|
||||
```Python hl_lines="1 5-7" title="app/dependencies.py"
|
||||
{!> ../../docs_src/bigger_applications/app_an/dependencies.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+ non-Annotated
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Мы рекомендуем использовать версию `Annotated`, когда это возможно.
|
||||
|
||||
///
|
||||
|
||||
```Python hl_lines="1 4-6" title="app/dependencies.py"
|
||||
{!> ../../docs_src/bigger_applications/app/dependencies.py!}
|
||||
```
|
||||
|
||||
////
|
||||
{* ../../docs_src/bigger_applications/app_an_py39/dependencies.py hl[3,6:8] title["app/dependencies.py"] *}
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
|
|
@ -180,9 +148,7 @@ from app.routers import items
|
|||
Таким образом, вместо того чтобы добавлять все эти свойства в функцию каждого отдельного *эндпоинта*,
|
||||
мы добавим их в `APIRouter`.
|
||||
|
||||
```Python hl_lines="5-10 16 21" title="app/routers/items.py"
|
||||
{!../../docs_src/bigger_applications/app/routers/items.py!}
|
||||
```
|
||||
{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *}
|
||||
|
||||
Так как каждый *эндпоинт* начинается с символа `/`:
|
||||
|
||||
|
|
@ -241,9 +207,7 @@ async def read_item(item_id: str):
|
|||
|
||||
Мы используем операцию относительного импорта `..` для импорта зависимости:
|
||||
|
||||
```Python hl_lines="3" title="app/routers/items.py"
|
||||
{!../../docs_src/bigger_applications/app/routers/items.py!}
|
||||
```
|
||||
{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[3] title["app/routers/items.py"] *}
|
||||
|
||||
#### Как работает относительный импорт? { #how-relative-imports-work }
|
||||
|
||||
|
|
@ -313,9 +277,7 @@ from ...dependencies import get_token_header
|
|||
|
||||
Но помимо этого мы можем добавить новые теги для каждого отдельного *эндпоинта*, а также некоторые дополнительные ответы (`responses`), характерные для данного *эндпоинта*:
|
||||
|
||||
```Python hl_lines="30-31" title="app/routers/items.py"
|
||||
{!../../docs_src/bigger_applications/app/routers/items.py!}
|
||||
```
|
||||
{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[30:31] title["app/routers/items.py"] *}
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
|
|
@ -341,17 +303,13 @@ from ...dependencies import get_token_header
|
|||
|
||||
Мы даже можем объявить [глобальные зависимости](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!}
|
||||
```
|
||||
{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[1,3,7] title["app/main.py"] *}
|
||||
|
||||
### Импорт `APIRouter` { #import-the-apirouter }
|
||||
|
||||
Теперь мы импортируем другие суб-модули, содержащие `APIRouter`:
|
||||
|
||||
```Python hl_lines="4-5" title="app/main.py"
|
||||
{!../../docs_src/bigger_applications/app/main.py!}
|
||||
```
|
||||
{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[4:5] title["app/main.py"] *}
|
||||
|
||||
Так как файлы `app/routers/users.py` и `app/routers/items.py` являются суб-модулями одного и того же Python-пакета `app`, то мы сможем их импортировать, воспользовавшись операцией относительного импорта `.`.
|
||||
|
||||
|
|
@ -414,17 +372,13 @@ from .routers.users import router
|
|||
|
||||
Поэтому, для того чтобы использовать обе эти переменные в одном файле, мы импортировали соответствующие суб-модули:
|
||||
|
||||
```Python hl_lines="5" title="app/main.py"
|
||||
{!../../docs_src/bigger_applications/app/main.py!}
|
||||
```
|
||||
{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[5] title["app/main.py"] *}
|
||||
|
||||
### Подключение маршрутизаторов (`APIRouter`) для `users` и для `items` { #include-the-apirouters-for-users-and-items }
|
||||
|
||||
Давайте подключим маршрутизаторы (`router`) из суб-модулей `users` и `items`:
|
||||
|
||||
```Python hl_lines="10-11" title="app/main.py"
|
||||
{!../../docs_src/bigger_applications/app/main.py!}
|
||||
```
|
||||
{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[10:11] title["app/main.py"] *}
|
||||
|
||||
/// info | Примечание
|
||||
|
||||
|
|
@ -465,17 +419,13 @@ from .routers.users import router
|
|||
В данном примере это сделать очень просто. Но давайте предположим, что поскольку файл используется для нескольких проектов,
|
||||
то мы не можем модифицировать его, добавляя префиксы (`prefix`), зависимости (`dependencies`), теги (`tags`), и т.д. непосредственно в `APIRouter`:
|
||||
|
||||
```Python hl_lines="3" title="app/internal/admin.py"
|
||||
{!../../docs_src/bigger_applications/app/internal/admin.py!}
|
||||
```
|
||||
{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *}
|
||||
|
||||
Но, несмотря на это, мы хотим использовать кастомный префикс (`prefix`) для подключенного маршрутизатора (`APIRouter`), в результате чего, каждая *операция пути* будет начинаться с `/admin`. Также мы хотим защитить наш маршрутизатор с помощью зависимостей, созданных для нашего проекта. И ещё мы хотим включить теги (`tags`) и ответы (`responses`).
|
||||
|
||||
Мы можем применить все вышеперечисленные настройки, не изменяя начальный `APIRouter`. Нам всего лишь нужно передать нужные параметры в `app.include_router()`.
|
||||
|
||||
```Python hl_lines="14-17" title="app/main.py"
|
||||
{!../../docs_src/bigger_applications/app/main.py!}
|
||||
```
|
||||
{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[14:17] title["app/main.py"] *}
|
||||
|
||||
Таким образом, оригинальный `APIRouter` не будет модифицирован, и мы сможем использовать файл `app/internal/admin.py` сразу в нескольких проектах организации.
|
||||
|
||||
|
|
@ -496,9 +446,7 @@ from .routers.users import router
|
|||
|
||||
Здесь мы это делаем ... просто, чтобы показать, что это возможно 🤷:
|
||||
|
||||
```Python hl_lines="21-23" title="app/main.py"
|
||||
{!../../docs_src/bigger_applications/app/main.py!}
|
||||
```
|
||||
{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[21:23] title["app/main.py"] *}
|
||||
|
||||
и это будет работать корректно вместе с другими *эндпоинтами*, добавленными с помощью `app.include_router()`.
|
||||
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@
|
|||
|
||||
Вы можете сконфигурировать Pydantic-модель так, чтобы запретить (`forbid`) любые дополнительные (`extra`) поля:
|
||||
|
||||
{* ../../docs_src/cookie_param_models/tutorial002_an_py39.py hl[10] *}
|
||||
{* ../../docs_src/cookie_param_models/tutorial002_an_py310.py hl[10] *}
|
||||
|
||||
Если клиент попробует отправить **дополнительные cookies**, то в ответ он получит **ошибку**.
|
||||
|
||||
|
|
|
|||
|
|
@ -143,6 +143,42 @@ OpenAPI определяет схему API для вашего API. И эта
|
|||
|
||||
Вы также можете использовать её для автоматической генерации кода для клиентов, которые взаимодействуют с вашим API. Например, для фронтенд-, мобильных или IoT-приложений.
|
||||
|
||||
### Разверните приложение (необязательно) { #deploy-your-app-optional }
|
||||
|
||||
При желании вы можете развернуть своё приложение FastAPI в <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>, перейдите и присоединитесь к списку ожидания, если ещё не сделали этого. 🚀
|
||||
|
||||
Если у вас уже есть аккаунт **FastAPI Cloud** (мы пригласили вас из списка ожидания 😉), вы можете развернуть приложение одной командой.
|
||||
|
||||
Перед развертыванием убедитесь, что вы вошли в систему:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ fastapi login
|
||||
|
||||
You are logged in to FastAPI Cloud 🚀
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
Затем разверните приложение:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ fastapi deploy
|
||||
|
||||
Deploying to FastAPI Cloud...
|
||||
|
||||
✅ Deployment successful!
|
||||
|
||||
🐔 Ready the chicken! Your app is ready at https://myapp.fastapicloud.dev
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
Готово! Теперь вы можете открыть своё приложение по этому URL. ✨
|
||||
|
||||
## Рассмотрим поэтапно { #recap-step-by-step }
|
||||
|
||||
### Шаг 1: импортируйте `FastAPI` { #step-1-import-fastapi }
|
||||
|
|
@ -314,6 +350,26 @@ https://example.com/items/foo
|
|||
|
||||
Многие другие объекты и модели будут автоматически преобразованы в JSON (включая ORM и т. п.). Попробуйте использовать те, что вам привычнее, с высокой вероятностью они уже поддерживаются.
|
||||
|
||||
### Шаг 6: разверните приложение { #step-6-deploy-it }
|
||||
|
||||
Разверните приложение в **<a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>** одной командой: `fastapi deploy`. 🎉
|
||||
|
||||
#### О FastAPI Cloud { #about-fastapi-cloud }
|
||||
|
||||
**<a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>** создан тем же автором и командой, что и **FastAPI**.
|
||||
|
||||
Он упрощает процесс **создания образа**, **развертывания** и **доступа** к API с минимальными усилиями.
|
||||
|
||||
Он переносит тот же **опыт разработчика** при создании приложений с FastAPI на их **развертывание** в облаке. 🎉
|
||||
|
||||
FastAPI Cloud — основной спонсор и источник финансирования для open-source проектов «FastAPI и друзья». ✨
|
||||
|
||||
#### Развертывание у других облачных провайдеров { #deploy-to-other-cloud-providers }
|
||||
|
||||
FastAPI — open-source и основан на стандартах. Вы можете развернуть приложения FastAPI у любого облачного провайдера по вашему выбору.
|
||||
|
||||
Следуйте руководствам вашего облачного провайдера, чтобы развернуть приложения FastAPI у них. 🤓
|
||||
|
||||
## Резюме { #recap }
|
||||
|
||||
* Импортируйте `FastAPI`.
|
||||
|
|
@ -321,3 +377,4 @@ https://example.com/items/foo
|
|||
* Напишите **декоратор операции пути**, например `@app.get("/")`.
|
||||
* Определите **функцию операции пути**; например, `def root(): ...`.
|
||||
* Запустите сервер разработки командой `fastapi dev`.
|
||||
* При желании разверните приложение командой `fastapi deploy`.
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@
|
|||
|
||||
## Установка пользовательских обработчиков исключений { #install-custom-exception-handlers }
|
||||
|
||||
Вы можете добавить пользовательские обработчики исключений с помощью <a href="https://www.starlette.dev/exceptions/" class="external-link" target="_blank">то же самое исключение - утилиты от Starlette</a>.
|
||||
Вы можете добавить пользовательские обработчики исключений с помощью <a href="https://www.starlette.dev/exceptions/" class="external-link" target="_blank">тех же утилит обработки исключений из Starlette</a>.
|
||||
|
||||
Допустим, у вас есть пользовательское исключение `UnicornException`, которое вы (или используемая вами библиотека) можете `вызвать`.
|
||||
|
||||
|
|
@ -117,7 +117,7 @@
|
|||
|
||||
Вы можете переопределить эти обработчики исключений на свои собственные.
|
||||
|
||||
### Переопределение исключений проверки запроса { #override-request-validation-exceptions }
|
||||
### Переопределение обработчика исключений проверки запроса { #override-request-validation-exceptions }
|
||||
|
||||
Когда запрос содержит недопустимые данные, **FastAPI** внутренне вызывает ошибку `RequestValidationError`.
|
||||
|
||||
|
|
@ -127,7 +127,7 @@
|
|||
|
||||
Обработчик исключения получит объект `Request` и исключение.
|
||||
|
||||
{* ../../docs_src/handling_errors/tutorial004.py hl[2,14:16] *}
|
||||
{* ../../docs_src/handling_errors/tutorial004.py hl[2,14:19] *}
|
||||
|
||||
Теперь, если перейти к `/items/foo`, то вместо стандартной JSON-ошибки с:
|
||||
|
||||
|
|
@ -149,36 +149,17 @@
|
|||
вы получите текстовую версию:
|
||||
|
||||
```
|
||||
1 validation error
|
||||
path -> item_id
|
||||
value is not a valid integer (type=type_error.integer)
|
||||
Validation errors:
|
||||
Field: ('path', 'item_id'), Error: Input should be a valid integer, unable to parse string as an integer
|
||||
```
|
||||
|
||||
#### `RequestValidationError` или `ValidationError` { #requestvalidationerror-vs-validationerror }
|
||||
|
||||
/// warning | Внимание
|
||||
|
||||
Это технические детали, которые можно пропустить, если они не важны для вас сейчас.
|
||||
|
||||
///
|
||||
|
||||
`RequestValidationError` является подклассом Pydantic <a href="https://docs.pydantic.dev/latest/concepts/models/#error-handling" class="external-link" target="_blank">`ValidationError`</a>.
|
||||
|
||||
**FastAPI** использует его для того, чтобы, если вы используете Pydantic-модель в `response_model`, и ваши данные содержат ошибку, вы увидели ошибку в журнале.
|
||||
|
||||
Но клиент/пользователь этого не увидит. Вместо этого клиент получит сообщение "Internal Server Error" с кодом состояния HTTP `500`.
|
||||
|
||||
Так и должно быть, потому что если в вашем *ответе* или где-либо в вашем коде (не в *запросе* клиента) возникает Pydantic `ValidationError`, то это действительно ошибка в вашем коде.
|
||||
|
||||
И пока вы не устраните ошибку, ваши клиенты/пользователи не должны иметь доступа к внутренней информации о ней, так как это может привести к уязвимости в системе безопасности.
|
||||
|
||||
### Переопределите обработчик ошибок `HTTPException` { #override-the-httpexception-error-handler }
|
||||
|
||||
Аналогичным образом можно переопределить обработчик `HTTPException`.
|
||||
|
||||
Например, для этих ошибок можно вернуть обычный текстовый ответ вместо JSON:
|
||||
|
||||
{* ../../docs_src/handling_errors/tutorial004.py hl[3:4,9:11,22] *}
|
||||
{* ../../docs_src/handling_errors/tutorial004.py hl[3:4,9:11,25] *}
|
||||
|
||||
/// note | Технические детали
|
||||
|
||||
|
|
@ -188,6 +169,14 @@ path -> item_id
|
|||
|
||||
///
|
||||
|
||||
/// warning | Внимание
|
||||
|
||||
Имейте в виду, что `RequestValidationError` содержит информацию об имени файла и строке, где произошла ошибка валидации, чтобы вы могли при желании отобразить её в логах с релевантными данными.
|
||||
|
||||
Но это означает, что если вы просто преобразуете её в строку и вернёте эту информацию напрямую, вы можете допустить небольшую утечку информации о своей системе, поэтому здесь код извлекает и показывает каждую ошибку отдельно.
|
||||
|
||||
///
|
||||
|
||||
### Используйте тело `RequestValidationError` { #use-the-requestvalidationerror-body }
|
||||
|
||||
Ошибка `RequestValidationError` содержит полученное `тело` с недопустимыми данными.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Настройка авторизации
|
||||
# Настройка авторизации { #security }
|
||||
|
||||
Существует множество способов обеспечения безопасности, аутентификации и авторизации.
|
||||
|
||||
|
|
@ -10,11 +10,11 @@
|
|||
|
||||
Но сначала давайте рассмотрим некоторые небольшие концепции.
|
||||
|
||||
## Куда-то торопишься?
|
||||
## Куда-то торопишься? { #in-a-hurry }
|
||||
|
||||
Если вам не нужна информация о каких-либо из следующих терминов и вам просто нужно добавить защиту с аутентификацией на основе логина и пароля *прямо сейчас*, переходите к следующим главам.
|
||||
|
||||
## OAuth2
|
||||
## OAuth2 { #oauth2 }
|
||||
|
||||
OAuth2 - это протокол, который определяет несколько способов обработки аутентификации и авторизации.
|
||||
|
||||
|
|
@ -24,7 +24,7 @@ OAuth2 включает в себя способы аутентификации
|
|||
|
||||
Это то, что используют под собой все кнопки "вход с помощью Facebook, Google, X (Twitter), GitHub" на страницах авторизации.
|
||||
|
||||
### OAuth 1
|
||||
### OAuth 1 { #oauth-1 }
|
||||
|
||||
Ранее использовался протокол OAuth 1, который сильно отличается от OAuth2 и является более сложным, поскольку он включал прямые описания того, как шифровать сообщение.
|
||||
|
||||
|
|
@ -34,11 +34,11 @@ OAuth2 не указывает, как шифровать сообщение, о
|
|||
|
||||
/// tip | Подсказка
|
||||
|
||||
В разделе **Развертывание** вы увидите [как настроить протокол HTTPS бесплатно, используя Traefik и Let's Encrypt.](https://fastapi.tiangolo.com/ru/deployment/https/)
|
||||
В разделе **Развертывание** вы увидите как настроить протокол HTTPS бесплатно, используя Traefik и Let's Encrypt.
|
||||
|
||||
///
|
||||
|
||||
## OpenID Connect
|
||||
## OpenID Connect { #openid-connect }
|
||||
|
||||
OpenID Connect - это еще один протокол, основанный на **OAuth2**.
|
||||
|
||||
|
|
@ -48,7 +48,7 @@ OpenID Connect - это еще один протокол, основанный
|
|||
|
||||
Но вход в Facebook не поддерживает OpenID Connect. У него есть собственная вариация OAuth2.
|
||||
|
||||
### OpenID (не "OpenID Connect")
|
||||
### OpenID (не "OpenID Connect") { #openid-not-openid-connect }
|
||||
|
||||
Также ранее использовался стандарт "OpenID", который пытался решить ту же проблему, что и **OpenID Connect**, но не был основан на OAuth2.
|
||||
|
||||
|
|
@ -56,7 +56,7 @@ OpenID Connect - это еще один протокол, основанный
|
|||
|
||||
В настоящее время не очень популярен и не используется.
|
||||
|
||||
## OpenAPI
|
||||
## OpenAPI { #openapi }
|
||||
|
||||
OpenAPI (ранее известный как Swagger) - это открытая спецификация для создания API (в настоящее время является частью Linux Foundation).
|
||||
|
||||
|
|
@ -97,7 +97,7 @@ OpenAPI может использовать следующие схемы авт
|
|||
|
||||
///
|
||||
|
||||
## Преимущества **FastAPI**
|
||||
## Преимущества **FastAPI** { #fastapi-utilities }
|
||||
|
||||
Fast API предоставляет несколько инструментов для каждой из этих схем безопасности в модуле `fastapi.security`, которые упрощают использование этих механизмов безопасности.
|
||||
|
||||
|
|
|
|||
|
|
@ -63,9 +63,9 @@ $ pip install sqlmodel
|
|||
|
||||
* `table=True` сообщает SQLModel, что это *модель-таблица*, она должна представлять **таблицу** в SQL базе данных, это не просто *модель данных* (как обычный класс Pydantic).
|
||||
|
||||
* `Field(primary_key=True)` сообщает SQLModel, что `id` — это **первичный ключ** в SQL базе данных (подробнее о первичных ключах можно узнать в документации SQLModel).
|
||||
* `Field(primary_key=True)` сообщает SQLModel, что `id` — это **первичный ключ** в SQL базе данных (подробнее о первичных ключах SQL можно узнать в документации SQLModel).
|
||||
|
||||
Благодаря типу `int | None`, SQLModel будет знать, что этот столбец должен быть `INTEGER` в SQL базе данных и должен допускать значение `NULL`.
|
||||
**Примечание:** Мы используем `int | None` для поля первичного ключа, чтобы в Python-коде можно было *создать объект без `id`* (`id=None`), предполагая, что база данных *сгенерирует его при сохранении*. SQLModel понимает, что база данных предоставит `id`, и *определяет столбец как `INTEGER` (не `NULL`)* в схеме базы данных. См. <a href="https://sqlmodel.tiangolo.com/tutorial/create-db-and-table/#primary-key-id" class="external-link" target="_blank">документацию SQLModel о первичных ключах</a> для подробностей.
|
||||
|
||||
* `Field(index=True)` сообщает SQLModel, что нужно создать **SQL индекс** для этого столбца, что позволит быстрее выполнять выборки при чтении данных, отфильтрованных по этому столбцу.
|
||||
|
||||
|
|
@ -107,7 +107,7 @@ $ pip install sqlmodel
|
|||
|
||||
Здесь мы создаём таблицы в обработчике события запуска приложения.
|
||||
|
||||
Для продакшна вы, вероятно, будете использовать скрипт миграций, который выполняется до запуска приложения. 🤓
|
||||
Для продакшн вы, вероятно, будете использовать скрипт миграций, который выполняется до запуска приложения. 🤓
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
|
|
|
|||
|
|
@ -121,63 +121,13 @@ $ pip install httpx
|
|||
|
||||
Обе *операции пути* требуют наличия в запросе заголовка `X-Token`.
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python
|
||||
{!> ../../docs_src/app_testing/app_b_an_py310/main.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.9+
|
||||
|
||||
```Python
|
||||
{!> ../../docs_src/app_testing/app_b_an_py39/main.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+
|
||||
|
||||
```Python
|
||||
{!> ../../docs_src/app_testing/app_b_an/main.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.10+ без Annotated
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
По возможности используйте версию с `Annotated`.
|
||||
|
||||
///
|
||||
|
||||
```Python
|
||||
{!> ../../docs_src/app_testing/app_b_py310/main.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+ без Annotated
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
По возможности используйте версию с `Annotated`.
|
||||
|
||||
///
|
||||
|
||||
```Python
|
||||
{!> ../../docs_src/app_testing/app_b/main.py!}
|
||||
```
|
||||
|
||||
////
|
||||
{* ../../docs_src/app_testing/app_b_an_py310/main.py *}
|
||||
|
||||
### Расширенный файл тестов { #extended-testing-file }
|
||||
|
||||
Теперь обновим файл `test_main.py`, добавив в него тестов:
|
||||
|
||||
{* ../../docs_src/app_testing/app_b/test_main.py *}
|
||||
{* ../../docs_src/app_testing/app_b_an_py310/test_main.py *}
|
||||
|
||||
|
||||
Если Вы не знаете, как передать информацию в запросе, можете воспользоваться поисковиком (погуглить) и задать вопрос: "Как передать информацию в запросе с помощью `httpx`", можно даже спросить: "Как передать информацию в запросе с помощью `requests`", поскольку дизайн HTTPX основан на дизайне Requests.
|
||||
|
|
|
|||
|
|
@ -242,6 +242,26 @@ $ python -m pip install --upgrade pip
|
|||
|
||||
</div>
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Иногда при попытке обновить pip вы можете получить ошибку **`No module named pip`**.
|
||||
|
||||
Если это произошло, установите и обновите pip с помощью команды ниже:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ python -m ensurepip --upgrade
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
Эта команда установит pip, если он ещё не установлен, а также гарантирует, что установленная версия pip будет не старее, чем версия, доступная в `ensurepip`.
|
||||
|
||||
///
|
||||
|
||||
## Добавление `.gitignore` { #add-gitignore }
|
||||
|
||||
Если вы используете **Git** (а вам стоит его использовать), добавьте файл `.gitignore`, чтобы исключить из Git всё, что находится в вашей `.venv`.
|
||||
|
|
@ -834,7 +854,7 @@ I solemnly swear 🐺
|
|||
* Управлять **виртуальным окружением** ваших проектов
|
||||
* Устанавливать **пакеты**
|
||||
* Управлять **зависимостями и версиями** пакетов вашего проекта
|
||||
* Обеспечивать наличие **точного** набора пакетов и версий к установке, включая их зависимости, чтобы вы были уверены, что сможете запускать проект в продакшне точно так же, как и на компьютере при разработке — это называется **locking**
|
||||
* Обеспечивать наличие **точного** набора пакетов и версий к установке, включая их зависимости, чтобы вы были уверены, что сможете запускать проект в продакшн точно так же, как и на компьютере при разработке — это называется **locking**
|
||||
* И многое другое
|
||||
|
||||
## Заключение { #conclusion }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
|
||||
|
||||
__version__ = "0.124.2"
|
||||
__version__ = "0.124.3"
|
||||
|
||||
from starlette import status as status
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ from typing import (
|
|||
from fastapi._compat import may_v1, shared
|
||||
from fastapi.openapi.constants import REF_TEMPLATE
|
||||
from fastapi.types import IncEx, ModelNameMap, UnionType
|
||||
from pydantic import BaseModel, ConfigDict, TypeAdapter, create_model
|
||||
from pydantic import BaseModel, ConfigDict, Field, TypeAdapter, create_model
|
||||
from pydantic import PydanticSchemaGenerationError as PydanticSchemaGenerationError
|
||||
from pydantic import PydanticUndefinedAnnotation as PydanticUndefinedAnnotation
|
||||
from pydantic import ValidationError as ValidationError
|
||||
|
|
@ -50,6 +50,45 @@ UndefinedType = PydanticUndefinedType
|
|||
evaluate_forwardref = eval_type_lenient
|
||||
Validator = Any
|
||||
|
||||
# TODO: remove when dropping support for Pydantic < v2.12.3
|
||||
_Attrs = {
|
||||
"default": ...,
|
||||
"default_factory": None,
|
||||
"alias": None,
|
||||
"alias_priority": None,
|
||||
"validation_alias": None,
|
||||
"serialization_alias": None,
|
||||
"title": None,
|
||||
"field_title_generator": None,
|
||||
"description": None,
|
||||
"examples": None,
|
||||
"exclude": None,
|
||||
"exclude_if": None,
|
||||
"discriminator": None,
|
||||
"deprecated": None,
|
||||
"json_schema_extra": None,
|
||||
"frozen": None,
|
||||
"validate_default": None,
|
||||
"repr": True,
|
||||
"init": None,
|
||||
"init_var": None,
|
||||
"kw_only": None,
|
||||
}
|
||||
|
||||
|
||||
# TODO: remove when dropping support for Pydantic < v2.12.3
|
||||
def asdict(field_info: FieldInfo) -> Dict[str, Any]:
|
||||
attributes = {}
|
||||
for attr in _Attrs:
|
||||
value = getattr(field_info, attr, Undefined)
|
||||
if value is not Undefined:
|
||||
attributes[attr] = value
|
||||
return {
|
||||
"annotation": field_info.annotation,
|
||||
"metadata": field_info.metadata,
|
||||
"attributes": attributes,
|
||||
}
|
||||
|
||||
|
||||
class BaseConfig:
|
||||
pass
|
||||
|
|
@ -107,10 +146,15 @@ class ModelField:
|
|||
warnings.simplefilter(
|
||||
"ignore", category=UnsupportedFieldAttributeWarning
|
||||
)
|
||||
# TODO: remove after dropping support for Python 3.8 and
|
||||
# setting the min Pydantic to v2.12.3 that adds asdict()
|
||||
field_dict = asdict(self.field_info)
|
||||
annotated_args = (
|
||||
self.field_info.annotation,
|
||||
*self.field_info.metadata,
|
||||
self.field_info,
|
||||
field_dict["annotation"],
|
||||
*field_dict["metadata"],
|
||||
# this FieldInfo needs to be created again so that it doesn't include
|
||||
# the old field info metadata and only the rest of the attributes
|
||||
Field(**field_dict["attributes"]),
|
||||
)
|
||||
self._type_adapter: TypeAdapter[Any] = TypeAdapter(
|
||||
Annotated[annotated_args],
|
||||
|
|
|
|||
|
|
@ -0,0 +1,207 @@
|
|||
# Ref: https://github.com/fastapi/fastapi/discussions/14495
|
||||
|
||||
from typing import Union
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
from inline_snapshot import snapshot
|
||||
from pydantic import BaseModel
|
||||
from typing_extensions import Annotated
|
||||
|
||||
from .utils import needs_pydanticv2
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def client_fixture() -> TestClient:
|
||||
from fastapi import Body
|
||||
from pydantic import Discriminator, Tag
|
||||
|
||||
class Cat(BaseModel):
|
||||
pet_type: str = "cat"
|
||||
meows: int
|
||||
|
||||
class Dog(BaseModel):
|
||||
pet_type: str = "dog"
|
||||
barks: float
|
||||
|
||||
def get_pet_type(v):
|
||||
assert isinstance(v, dict)
|
||||
return v.get("pet_type", "")
|
||||
|
||||
Pet = Annotated[
|
||||
Union[Annotated[Cat, Tag("cat")], Annotated[Dog, Tag("dog")]],
|
||||
Discriminator(get_pet_type),
|
||||
]
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
@app.post("/pet/assignment")
|
||||
async def create_pet_assignment(pet: Pet = Body()):
|
||||
return pet
|
||||
|
||||
@app.post("/pet/annotated")
|
||||
async def create_pet_annotated(pet: Annotated[Pet, Body()]):
|
||||
return pet
|
||||
|
||||
client = TestClient(app)
|
||||
return client
|
||||
|
||||
|
||||
@needs_pydanticv2
|
||||
def test_union_body_discriminator_assignment(client: TestClient) -> None:
|
||||
response = client.post("/pet/assignment", json={"pet_type": "cat", "meows": 5})
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {"pet_type": "cat", "meows": 5}
|
||||
|
||||
|
||||
@needs_pydanticv2
|
||||
def test_union_body_discriminator_annotated(client: TestClient) -> None:
|
||||
response = client.post("/pet/annotated", json={"pet_type": "dog", "barks": 3.5})
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {"pet_type": "dog", "barks": 3.5}
|
||||
|
||||
|
||||
@needs_pydanticv2
|
||||
def test_openapi_schema(client: TestClient) -> None:
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == snapshot(
|
||||
{
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/pet/assignment": {
|
||||
"post": {
|
||||
"summary": "Create Pet Assignment",
|
||||
"operationId": "create_pet_assignment_pet_assignment_post",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{"$ref": "#/components/schemas/Cat"},
|
||||
{"$ref": "#/components/schemas/Dog"},
|
||||
],
|
||||
"title": "Pet",
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": True,
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
"/pet/annotated": {
|
||||
"post": {
|
||||
"summary": "Create Pet Annotated",
|
||||
"operationId": "create_pet_annotated_pet_annotated_post",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"oneOf": [
|
||||
{"$ref": "#/components/schemas/Cat"},
|
||||
{"$ref": "#/components/schemas/Dog"},
|
||||
],
|
||||
"title": "Pet",
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": True,
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Cat": {
|
||||
"properties": {
|
||||
"pet_type": {
|
||||
"type": "string",
|
||||
"title": "Pet Type",
|
||||
"default": "cat",
|
||||
},
|
||||
"meows": {"type": "integer", "title": "Meows"},
|
||||
},
|
||||
"type": "object",
|
||||
"required": ["meows"],
|
||||
"title": "Cat",
|
||||
},
|
||||
"Dog": {
|
||||
"properties": {
|
||||
"pet_type": {
|
||||
"type": "string",
|
||||
"title": "Pet Type",
|
||||
"default": "dog",
|
||||
},
|
||||
"barks": {"type": "number", "title": "Barks"},
|
||||
},
|
||||
"type": "object",
|
||||
"required": ["barks"],
|
||||
"title": "Dog",
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"properties": {
|
||||
"detail": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ValidationError"
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Detail",
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"title": "HTTPValidationError",
|
||||
},
|
||||
"ValidationError": {
|
||||
"properties": {
|
||||
"loc": {
|
||||
"items": {
|
||||
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Location",
|
||||
},
|
||||
"msg": {"type": "string", "title": "Message"},
|
||||
"type": {"type": "string", "title": "Error Type"},
|
||||
},
|
||||
"type": "object",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"title": "ValidationError",
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
Loading…
Reference in New Issue