diff --git a/docs/ru/docs/advanced/additional-responses.md b/docs/ru/docs/advanced/additional-responses.md new file mode 100644 index 000000000..9a61beacf --- /dev/null +++ b/docs/ru/docs/advanced/additional-responses.md @@ -0,0 +1,247 @@ +# Дополнительные ответы в OpenAPI + +/// warning | Предупреждение + +Это достаточно сложная тема. + +Если вы только начинаете работать с **FastAPI**, вам это может не понадобиться. + +/// + +Вы можете объявлять дополнительные ответы с дополнительными статус-кодами, типами содержимого, описаниями и т.д. + +Эти дополнительные ответы будут включены в схему OpenAPI, так что они также появятся в документации API. + +Но для этих дополнительных ответов вы должны убедиться, что возвращаете `Response`, например, `JSONResponse` напрямую, с вашим статус-кодом и содержимым. + +## Дополнительный ответ с `model` + +Вы можете передать своим декораторам *операций пути* параметр `responses`. + +Он принимает `dict`: ключами являются статус-коды для каждого ответа (например, `200`), а значениями являются другие `dict` с информацией для каждого из них. + +Каждый из этих `dict` ответа может содержать ключ `model`, содержащий Pydantic-модель, как и `response_model`. + +**FastAPI** возьмет эту модель, сгенерирует ее JSON Schema и включит в нужное место в OpenAPI. + +Например, чтобы объявить другой ответ с кодом состояния `404` и Pydantic-моделью `Message`, вы можете написать: + +{* ../../docs_src/additional_responses/tutorial001.py hl[18,22] *} + +/// note | Заметка + +Имейте в виду, что вы должны вернуть `JSONResponse` напрямую. + +/// + +/// info | Информация + +Ключ `model` не является частью OpenAPI. + +**FastAPI** возьмет Pydantic-модель оттуда, сгенерирует JSON Schema и поместит в нужное место. + +Правильное место: + +* В ключе `content`, который имеет значение другой JSON объект (`dict`), содержащий: + * Ключ с типом содержимого, например `application/json`, который в качестве значения содержит другой JSON объект, содержащий: + * Ключ `schema`, который имеет в качестве значения JSON Schema из модели, вот куда нужно поместить. + * **FastAPI** добавляет сюда ссылку на глобальные JSON Schemas в другом месте вашего OpenAPI вместо того, чтобы включать его напрямую. Таким образом, другие приложения и клиенты могут использовать эти JSON Schemas напрямую, предоставлять лучшие инструменты генерации кода и другие функции. + +/// + +Сгенерированные ответы в OpenAPI для этой *операции пути* будут: + +```JSON hl_lines="3-12" +{ + "responses": { + "404": { + "description": "Дополнительный ответ", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Message" + } + } + } + }, + "200": { + "description": "Успешный ответ", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Item" + } + } + } + }, + "422": { + "description": "Ошибка валидации", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } +} +``` + +Схемы ссылаются на другое место внутри схемы OpenAPI: + +```JSON hl_lines="4-16" +{ + "components": { + "schemas": { + "Message": { + "title": "Сообщение", + "required": [ + "сообщение" + ], + "type": "object", + "properties": { + "сообщение": { + "title": "Сообщение", + "type": "string" + } + } + }, + "Item": { + "title": "Предмет", + "required": [ + "идентификатор", + "значение" + ], + "type": "object", + "properties": { + "идентификатор": { + "title": "Идентификатор", + "type": "string" + }, + "значение": { + "title": "Значение", + "type": "string" + } + } + }, + "ValidationError": { + "title": "Ошибка валидации", + "required": [ + "местоположение", + "сообщение", + "тип" + ], + "type": "object", + "properties": { + "местоположение": { + "title": "Location", + "type": "array", + "items": { + "type": "string" + } + }, + "сообщение": { + "title": "Сообщение", + "type": "string" + }, + "тип": { + "title": "Тип ошибки", + "type": "string" + } + } + }, + "HTTPValidationError": { + "title": "Ошибка валидации HTTP", + "type": "object", + "properties": { + "детали": { + "title": "Детали", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + } + } + } + } + } + } +} +``` + +## Дополнительные типы содержимого для основного ответа + +Вы можете использовать этот же параметр `responses`, чтобы добавить разные типы содержимого для одного и того же основного ответа. + +Например, вы можете добавить дополнительный медиа-тип `image/png`, объявив, что ваши *операции пути* могут возвращать JSON объект (с медиа-типом `application/json`) или изображение PNG: + +{* ../../docs_src/additional_responses/tutorial002.py hl[19:24,28] *} + +/// note | Заметка + +Обратите внимание, что вы должны возвращать изображение, используя `FileResponse` напрямую. + +/// + +/// info | Информация + +Если вы явно не указываете другой тип содержимого в вашем параметре `responses`, FastAPI предположит, что ответ имеет тот же тип содержимого, что и основной класс ответа (по умолчанию `application/json`). + +Но если вы задали пользовательский класс ответа с `None` в качестве типа содержимого, FastAPI будет использовать `application/json` для любого дополнительного ответа, у которого есть ассоциированная модель. + +/// + +## Комбинирование информации + +Вы также можете комбинировать информацию о ответах из разных мест, включая параметры `response_model`, `status_code` и `responses`. + +Вы можете объявить `response_model`, используя стандартный статус-код `200` (или пользовательский, если необходимо), а затем объявить дополнительную информацию для этого же ответа в `responses`, напрямую в схеме OpenAPI. + +**FastAPI** сохранит дополнительную информацию из `responses` и объединит ее с JSON Schema из вашей модели. + +Например, вы можете объявить ответ с кодом состояния `404`, который использует Pydantic-модель и имеет пользовательское `description`. + +И ответ с кодом состояния `200`, который использует ваш `response_model`, но включает пользовательский `example`: + +{* ../../docs_src/additional_responses/tutorial003.py hl[20:31] *} + +Все это будет объединено и включено в ваш OpenAPI, и показано в документации API: + + + +## Комбинирование предопределенных и пользовательских ответов + +Возможно, вы захотите иметь некоторые предопределенные ответы, которые применяются ко многим *операциям пути*, но хотите объединить их с пользовательскими ответами, необходимыми для каждой *операции пути*. + +Для таких случаев вы можете использовать технику Python "распаковка" `dict` с помощью `**dict_to_unpack`: + +```Python +old_dict = { + "old key": "old value", + "second old key": "second old value", +} +new_dict = {**old_dict, "new key": "new value"} +``` + +Здесь, `new_dict` будет содержать все пары `ключ-значение` из `old_dict` плюс новую пару `ключ-значение`: + +```Python +{ + "old key": "old value", + "second old key": "second old value", + "new key": "new value", +} +``` + +Вы можете использовать эту технику для повторного использования некоторых предопределенных ответов в ваших *операциях пути* и объединить их с дополнительными пользовательскими. + +Например: + +{* ../../docs_src/additional_responses/tutorial004.py hl[13:17,26] *} + +## Больше информации об ответах OpenAPI + +Чтобы узнать, что именно можно включить в ответы, вы можете посмотреть эти разделы в спецификации OpenAPI: + +* OpenAPI Responses Object, включает в себя `Response Object`. +* OpenAPI Response Object, вы можете включить что угодно из этого напрямую в каждый ответ внутри вашего параметра `responses`. Включая `description`, `headers`, `content` (внутри этого вы указываете разные типы медиа и JSON Schemas), и `links`. diff --git a/docs/ru/docs/advanced/advanced-dependencies.md b/docs/ru/docs/advanced/advanced-dependencies.md new file mode 100644 index 000000000..2a44a479e --- /dev/null +++ b/docs/ru/docs/advanced/advanced-dependencies.md @@ -0,0 +1,65 @@ +# Продвинутые зависимости + +## Параметризуемые зависимости + +Все зависимости, которые мы видели, являются фиксированной функцией или классом. + +Но могут быть случаи, когда вы захотите иметь возможность задавать параметры для зависимости, не объявляя множество разных функций или классов. + +Представим, что мы хотим иметь зависимость, которая проверяет, содержит ли параметр запроса `q` некоторое фиксированное содержимое. + +Но нам нужно иметь возможность параметризовать это фиксированное содержимое. + +## "Вызываемый" экземпляр + +В Python есть способ сделать экземпляр класса "вызываемым". + +Не сам класс (он уже может быть вызываемым), а экземпляр этого класса. + +Для этого мы объявляем метод `__call__`: + +{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[12] *} + +В этом случае **FastAPI** будет использовать этот `__call__` для проверки дополнительных параметров и под-зависимостей, и он будет вызываться для передачи значения в параметр вашей *функции-обработчика пути* позже. + +## Параметризация экземпляра + +Теперь мы можем использовать `__init__`, чтобы объявить параметры экземпляра, которые мы можем использовать для "параметризации" зависимости: + +{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[9] *} + +В данном случае **FastAPI** никогда не будет касаться или заботиться об `__init__`, мы будем использовать его напрямую в нашем коде. + +## Создание экземпляра + +Мы можем создать экземпляр этого класса с помощью: + +{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[18] *} + +Таким образом, мы можем "параметризовать" нашу зависимость, в которой теперь есть `"bar"`, как атрибут `checker.fixed_content`. + +## Использование экземпляра в качестве зависимости + +Затем мы могли бы использовать этот `checker` в `Depends(checker)`, вместо `Depends(FixedContentQueryChecker)`, потому что зависимость — это экземпляр `checker`, а не сам класс. + +И при разрешении зависимости, **FastAPI** вызовет этот `checker` так: + +```Python +checker(q="somequery") +``` + +...и передаст то, что это возвращает, как значение зависимости в нашей *функции-обработчике пути* как параметр `fixed_content_included`: + +{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[22] *} + +/// tip | Совет + +Все это может показаться надуманным. И пока не совсем ясно, насколько это полезно. + +Эти примеры намеренно просты, но показывают, как все работает. + +В главах о безопасности есть утилиты-функции, которые реализованы таким же образом. + +Если вы все это поняли, вы уже знаете, как работают эти утилиты для безопасности изнутри. + +/// diff --git a/docs/ru/docs/advanced/behind-a-proxy.md b/docs/ru/docs/advanced/behind-a-proxy.md new file mode 100644 index 000000000..0d4b60c07 --- /dev/null +++ b/docs/ru/docs/advanced/behind-a-proxy.md @@ -0,0 +1,361 @@ +# За прокси-сервером + +В некоторых ситуациях вам может понадобиться использовать **прокси**-сервер, такой как Traefik или Nginx, с конфигурацией, добавляющей дополнительный префикс к path, который ваше приложение не видит. + +В таких случаях вы можете использовать `root_path` для настройки вашего приложения. + +`root_path` — это механизм, предоставляемый спецификацией ASGI (на основе которой построен FastAPI через Starlette). + +`root_path` используется для обработки этих конкретных случаев. + +Он также используется внутренне при монтировании подприложений. + +## Прокси с вырезанным префиксом path + +Использование прокси с вырезанным префиксом path означает, что вы можете объявить путь как `/app` в своем коде. Но затем вы добавляете слой сверху (прокси), который помещает ваше приложение **FastAPI** под путь, такой как `/api/v1`. + +В этом случае оригинальный путь `/app` будет фактически работать как `/api/v1/app`. + +Несмотря на то, что весь ваш код написан с учетом использования только `/app`. + +{* ../../docs_src/behind_a_proxy/tutorial001.py hl[6] *} + +И прокси будет **"вырезать"** **префикс пути** на лету перед передачей запроса серверу приложения (вероятно, Uvicorn через CLI FastAPI), оставляя ваше приложение уверенным, что оно работает на `/app`, чтобы вам не пришлось обновлять весь код для включения префикса `/api/v1`. + +До этого момента все будет работать как обычно. + +Но затем, когда вы откроете встроенный интерфейс документации (фронтенд), он будет ожидать получить OpenAPI-схему на `/openapi.json`, вместо `/api/v1/openapi.json`. + +Поэтому фронтенд (который работает в браузере) попытается достичь `/openapi.json` и не сможет получить OpenAPI-схему. + +Поскольку у нас есть прокси с префиксом пути `/api/v1` для нашего приложения, фронтенд должен получить OpenAPI-схему на `/api/v1/openapi.json`. + +```mermaid +graph LR + +browser("Браузер") +proxy["Прокси на http://0.0.0.0:9999/api/v1/app"] +server["Сервер на http://127.0.0.1:8000/app"] + +browser --> proxy +proxy --> server +``` + +/// совет | Совет + +IP `0.0.0.0` обычно используется, чтобы указать, что программа слушает все доступные IP-адреса на этой машине/сервере. + +/// + +Интерфейс документации также будет нуждаться в OpenAPI-схеме для декларации, что этот API `server` расположен на `/api/v1` (за прокси). Например: + +```JSON hl_lines="4-8" +{ + "openapi": "3.1.0", + // Другие данные здесь + "servers": [ + { + "url": "/api/v1" + } + ], + "paths": { + // Другие данные здесь + } +} +``` + +В этом примере "Прокси" может быть чем-то вроде **Traefik**. А сервером может быть что-то вроде CLI FastAPI с **Uvicorn**, который запускает ваше приложение FastAPI. + +### Указание `root_path` + +Чтобы достичь этого, вы можете использовать командную строку с опцией `--root-path`, например: + +
+ +```console +$ fastapi run main.py --root-path /api/v1 + +INFO: Uvicorn запущен на http://127.0.0.1:8000 (Нажмите CTRL+C для остановки) +``` + +
+ +Если вы используете Hypercorn, у него также есть опция `--root-path`. + +/// note | Технические подробности + +Спецификация ASGI определяет `root_path` для этого случая использования. + +И опция командной строки `--root-path` предоставляет этот `root_path`. + +/// + +### Проверка текущего `root_path` + +Вы можете получить текущий `root_path`, используемый вашим приложением для каждого запроса, он является частью словаря `scope` (это часть спецификации ASGI). + +Здесь мы включаем его в сообщение просто для демонстрации. + +{* ../../docs_src/behind_a_proxy/tutorial001.py hl[8] *} + +Затем, если вы запустите Uvicorn с: + +
+ +```console +$ fastapi run main.py --root-path /api/v1 + +INFO: Uvicorn запущен на http://127.0.0.1:8000 (Нажмите CTRL+C для остановки) +``` + +
+ +Ответ будет что-то вроде: + +```JSON +{ + "message": "Hello World", + "root_path": "/api/v1" +} +``` + +### Установка `root_path` в приложении FastAPI + +Альтернативно, если у вас нет возможности предоставить опцию командной строки, такую как `--root-path` или аналогичную, вы можете установить параметр `root_path` при создании вашего приложения FastAPI: + +{* ../../docs_src/behind_a_proxy/tutorial002.py hl[3] *} + +Передача `root_path` в `FastAPI` будет эквивалентна передаче опции командной строки `--root-path` для Uvicorn или Hypercorn. + +### О `root_path` + +Учтите, что сервер (Uvicorn) не будет использовать `root_path` ни для чего, кроме передачи его приложению. + +Но если вы зайдете через браузер по адресу http://127.0.0.1:8000/app, вы увидите обычный ответ: + +```JSON +{ + "message": "Hello World", + "root_path": "/api/v1" +} +``` + +Итак, он не будет ожидать доступа по адресу `http://127.0.0.1:8000/api/v1/app`. + +Uvicorn будет ожидать, что прокси получит доступ к Uvicorn по адресу `http://127.0.0.1:8000/app`, а затем это будет уже ответственность прокси — добавить дополнительный префикс `/api/v1`. + +## О прокси с вырезанным префиксом path + +Имейте в виду, что прокси с вырезанным префиксом path — это всего лишь один из способов его настройки. + +Вероятно, во многих случаях по умолчанию будет то, что у прокси нет вырезанного префикса path. + +В случае, подобном этому (без вырезанного префикса path), прокси будет слушать что-то вроде `https://myawesomeapp.com`, и тогда, если браузер перейдет на `https://myawesomeapp.com/api/v1/app`, а ваш сервер (например, Uvicorn) слушает на `http://127.0.0.1:8000`, прокси (без вырезанного префикса path) получит доступ к Uvicorn на том же пути: `http://127.0.0.1:8000/api/v1/app`. + +## Тестирование локально с Traefik + +Вы можете легко провести эксперимент локально с вырезанным префиксом path, используя Traefik. + +Скачайте Traefik, это один исполняемый файл, вы можете извлечь сжатый файл и запустить его прямо из терминала. + +Затем создайте файл `traefik.toml` с: + +```TOML hl_lines="3" +[entryPoints] + [entryPoints.http] + address = ":9999" + +[providers] + [providers.file] + filename = "routes.toml" +``` + +Это указывает Traefik слушать на порту 9999 и использовать другой файл `routes.toml`. + +/// совет + +Мы используем порт 9999 вместо стандартного порта HTTP 80, чтобы вам не пришлось запускать это с привилегиями администратора (`sudo`). + +/// + +Теперь создайте другой файл `routes.toml`: + +```TOML hl_lines="5 12 20" +[http] + [http.middlewares] + + [http.middlewares.api-stripprefix.stripPrefix] + prefixes = ["/api/v1"] + + [http.routers] + + [http.routers.app-http] + entryPoints = ["http"] + service = "app" + rule = "PathPrefix(`/api/v1`)" + middlewares = ["api-stripprefix"] + + [http.services] + + [http.services.app] + [http.services.app.loadBalancer] + [[http.services.app.loadBalancer.servers]] + url = "http://127.0.0.1:8000" +``` + +Этот файл настраивает Traefik для использования префикса path `/api/v1`. + +А затем Traefik перенаправит свои запросы на ваш Uvicorn, запущенный на `http://127.0.0.1:8000`. + +Теперь запустите Traefik: + +
+ +```console +$ ./traefik --configFile=traefik.toml + +INFO[0000] Конфигурация загружена из файла: /home/user/awesomeapi/traefik.toml +``` + +
+ +И теперь запустите ваше приложение, используя опцию `--root-path`: + +
+ +```console +$ fastapi run main.py --root-path /api/v1 + +INFO: Uvicorn запущен на http://127.0.0.1:8000 (Нажмите CTRL+C для остановки) +``` + +
+ +### Проверьте ответы + +Теперь, если вы перейдете по URL с портом для Uvicorn: http://127.0.0.1:8000/app, вы увидите нормальный ответ: + +```JSON +{ + "message": "Hello World", + "root_path": "/api/v1" +} +``` + +/// совет | Совет + +Обратите внимание, что, хотя вы получаете доступ к нему по адресу `http://127.0.0.1:8000/app`, он отображает `root_path` как `/api/v1`, взятый из опции `--root-path`. + +/// + +А теперь откройте URL с портом для Traefik, включая префикс path: http://127.0.0.1:9999/api/v1/app. + +Мы получаем такой же ответ: + +```JSON +{ + "message": "Hello World", + "root_path": "/api/v1" +} +``` + +но на этот раз по URL с префиксом path, предоставленным прокси: `/api/v1`. + +Конечно, идея здесь в том, что все должны получить доступ к приложению через прокси, так что версия с префиксом path `/api/v1` — это "правильная" версия. + +И версия без префикса path (`http://127.0.0.1:8000/app`), предоставляемая напрямую Uvicorn, будет исключительно для _прокси_ (Traefik), чтобы получить к ней доступ. + +Это демонстрирует, как прокси (Traefik) использует префикс path и как сервер (Uvicorn) использует `root_path` из опции `--root-path`. + +### Проверьте интерфейс документации + +Но вот где начинается веселье. ✨ + +"Официальный" способ доступа к приложению будет через прокси с префиксом path, который мы определили. Поэтому, как и ожидалось, если вы попытаетесь использовать интерфейс документации, обслуживаемый Uvicorn напрямую, без префикса path в URL, он не будет работать, потому что предполагается, что доступ будет осуществляться через прокси. + +Вы можете проверить это по адресу http://127.0.0.1:8000/docs: + + + +Но если мы получим доступ к интерфейсу документации по "официальному" URL, используя прокси с портом `9999`, по адресу `/api/v1/docs`, он работает правильно! 🎉 + +Вы можете проверить это по адресу http://127.0.0.1:9999/api/v1/docs: + + + +Как и хотели. ✔️ + +Это потому, что FastAPI использует этот `root_path` для создания `server` по умолчанию в OpenAPI с URL, предоставленным `root_path`. + +## Дополнительные серверы + +/// warning | Предупреждение + +Это более сложный случай использования. Вы можете пропустить его, если хотите. + +/// + +По умолчанию **FastAPI** создаст `server` в схеме OpenAPI с URL для `root_path`. + +Но вы также можете предоставить другие альтернативные `servers`, например, если вы хотите, чтобы *этот же* интерфейс документации взаимодействовал как со средой тестирования, так и со средой продакшн. + +Если вы передаете пользовательский список `servers` и существует `root_path` (потому что ваш API находится за прокси), **FastAPI** вставит "server" с этим `root_path` в начало списка. + +Например: + +{* ../../docs_src/behind_a_proxy/tutorial003.py hl[4:7] *} + +Создаст схему OpenAPI, как: + +```JSON hl_lines="5-7" +{ + "openapi": "3.1.0", + // Другие данные здесь + "servers": [ + { + "url": "/api/v1" + }, + { + "url": "https://stag.example.com", + "description": "Тестовая среда" + }, + { + "url": "https://prod.example.com", + "description": "Продакшн среда" + } + ], + "paths": { + // Другие данные здесь + } +} +``` + +/// совет | Совет + +Обратите внимание на автоматически сгенерированный сервер с `url` значением `/api/v1`, взятым из `root_path`. + +/// + +В интерфейсе документации по адресу http://127.0.0.1:9999/api/v1/docs это будет выглядеть так: + + + +/// совет | Совет + +Интерфейс документации будет взаимодействовать с сервером, который вы выберете. + +/// + +### Отключение автоматического сервера от `root_path` + +Если вы не хотите, чтобы **FastAPI** включал автоматический сервер, используя `root_path`, вы можете использовать параметр `root_path_in_servers=False`: + +{* ../../docs_src/behind_a_proxy/tutorial004.py hl[9] *} + +и тогда он не будет включен в схему OpenAPI. + +## Монтирование подприложения + +Если вам нужно смонтировать подприложение (как описано в [Подприложения - Монтирование](sub-applications.md){.internal-link target=_blank}), используя также прокси с `root_path`, вы можете сделать это обычным образом, как и ожидалось. + +FastAPI будет использовать `root_path` умно, поэтому все будет работать, как и задумано. ✨ diff --git a/docs/ru/docs/advanced/custom-response.md b/docs/ru/docs/advanced/custom-response.md new file mode 100644 index 000000000..182ec4909 --- /dev/null +++ b/docs/ru/docs/advanced/custom-response.md @@ -0,0 +1,313 @@ +# Пользовательский ответ - HTML, поток, файл и другие + +По умолчанию **FastAPI** будет возвращать ответы, используя `JSONResponse`. + +Вы можете переопределить это, возвращая `Response` напрямую, как показано в разделе [Возврат Response напрямую](response-directly.md){.internal-link target=_blank}. + +Но если вы возвращаете `Response` напрямую (или любой подкласс, такой как `JSONResponse`), данные не будут автоматически конвертироваться (даже если вы объявите `response_model`), и документация не будет автоматически генерироваться (например, включая конкретный "тип содержимого", в HTTP-заголовке `Content-Type` как часть генерируемого OpenAPI). + +Но вы также можете объявить `Response`, который хотите использовать (например, любой подкласс `Response`), в *декораторе операции пути*, используя параметр `response_class`. + +Содержимое, которое вы возвращаете из вашей *функции-обработчика пути*, будет помещено внутрь этого `Response`. + +И если этот `Response` имеет JSON тип содержимого (`application/json`), как в случае с `JSONResponse` и `UJSONResponse`, данные, которые вы возвращаете, будут автоматически конвертироваться (и фильтроваться) с помощью любого Pydantic `response_model`, который вы объявили в *декораторе операции пути*. + +/// note | Замечание + +Если вы используете класс ответа без типа содержимого, FastAPI ожидает, что ваш ответ не будет содержать контент, поэтому он не задокументирует формат ответа в автоматически генерируемой документации OpenAPI. + +/// + +## Использование `ORJSONResponse` + +Например, если вы заботитесь о производительности, вы можете установить и использовать `orjson` и установить ответ `ORJSONResponse`. + +Импортируйте класс (подкласс) `Response`, который вы хотите использовать, и объявите его в *декораторе операции пути*. + +Для больших ответов возврат `Response` напрямую значительно быстрее, чем возврат словаря. + +Это происходит потому, что по умолчанию FastAPI будет проверять каждый элемент внутри и убеждаться, что он сериализуем как JSON, используя тот же [Совместимый с JSON кодировщик](../tutorial/encoder.md){.internal-link target=_blank}, о котором рассказано в руководстве. Это позволяет вам возвращать **произвольные объекты**, например, модели базы данных. + +Но если вы уверены, что содержимое, которое вы возвращаете, **сериализуемо с JSON**, вы можете передать его напрямую в класс ответа и избежать дополнительной нагрузки, которую FastAPI имел бы, передавая возвращаемый контент через `jsonable_encoder` перед передачей его в класс ответа. + +{* ../../docs_src/custom_response/tutorial001b.py hl[2,7] *} + +/// info | Информация + +Параметр `response_class` также будет использоваться для определения "типа содержимого" ответа. + +В данном случае HTTP-заголовок `Content-Type` будет установлен в `application/json`. + +И он будет задокументирован как таковой в OpenAPI. + +/// + +/// tip | Совет + +`ORJSONResponse` доступен только в FastAPI, не в Starlette. + +/// + +## HTML-ответ + +Чтобы вернуть ответ с HTML напрямую из **FastAPI**, используйте `HTMLResponse`. + +* Импортируйте `HTMLResponse`. +* Передайте `HTMLResponse` в качестве параметра `response_class` вашего *декоратора операции пути*. + +{* ../../docs_src/custom_response/tutorial002.py hl[2,7] *} + +/// info | Информация + +Параметр `response_class` также будет использоваться для определения "типа содержимого" ответа. + +В этом случае HTTP-заголовок `Content-Type` будет установлен в `text/html`. + +И он будет задокументирован как таковой в OpenAPI. + +/// + +### Возврат `Response` + +Как показано в разделе [Возврат Response напрямую](response-directly.md){.internal-link target=_blank}, вы также можете переопределить ответ напрямую в вашей *операции пути*, возвращая его. + +Тот же пример, что и выше, возвращающий `HTMLResponse`, может выглядеть так: + +{* ../../docs_src/custom_response/tutorial003.py hl[2,7,19] *} + +/// warning | Предупреждение + +`Response`, возвращенный напрямую вашей *функцией-обработчиком пути*, не будет задокументирован в OpenAPI (например, `Content-Type` не будет задокументирован) и не будет виден в автоматически генерируемой интерактивной документации. + +/// + +/// info | Информация + +Конечно, фактический заголовок `Content-Type`, статус-код и др., будут из объекта `Response`, который вы вернули. + +/// + +### Документирование в OpenAPI и переопределение `Response` + +Если вы хотите переопределить ответ из функции, но при этом документация должна содержать "тип содержимого" в OpenAPI, вы можете использовать параметр `response_class` И вернуть объект `Response`. + +Тогда `response_class` будет использоваться только для документирования OpenAPI *операции пути*, но ваш `Response` будет использоваться как есть. + +#### Возврат `HTMLResponse` напрямую + +Например, это может быть что-то вроде: + +{* ../../docs_src/custom_response/tutorial004.py hl[7,21,23] *} + +В этом примере функция `generate_html_response()` уже генерирует и возвращает `Response` вместо возврата HTML в виде `str`. + +Возвращая результат вызова `generate_html_response()`, вы уже возвращаете `Response`, который переопределит поведение **FastAPI** по умолчанию. + +Но так как вы также передали `HTMLResponse` в `response_class`, **FastAPI** будет знать, как задокументировать это в OpenAPI и интерактивной документации как HTML с `text/html`: + + + +## Доступные ответы + +Вот некоторые из доступных ответов. + +Имейте в виду, что вы можете использовать `Response` для возврата чего-либо еще или даже создать собственный подкласс. + +/// note | Технические детали + +Вы также можете использовать `from starlette.responses import HTMLResponse`. + +**FastAPI** предоставляет те же `starlette.responses`, что и `fastapi.responses`, просто для вашего удобства, как разработчика. Но большинство доступных ответов поступают напрямую из Starlette. + +/// + +### `Response` + +Основной класс `Response`, все остальные ответы наследуются от него. + +Вы можете вернуть его напрямую. + +Он принимает следующие параметры: + +* `content` - `str` или `bytes`. +* `status_code` - `int` HTTP статус-код. +* `headers` - `dict` строк. +* `media_type` - `str`, который задает тип содержимого, например `"text/html"`. + +FastAPI (фактически Starlette) автоматически добавит заголовок Content-Length. Он также включит заголовок Content-Type, основанный на `media_type` и добавит кодировку для текстовых типов. + +{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *} + +### `HTMLResponse` + +Принимает текст или байты и возвращает HTML-ответ, как вы прочли выше. + +### `PlainTextResponse` + +Принимает текст или байты и возвращает ответ с простым текстом. + +{* ../../docs_src/custom_response/tutorial005.py hl[2,7,9] *} + +### `JSONResponse` + +Принимает данные и возвращает ответ, кодированный как `application/json`. + +Это ответ по умолчанию, используемый в **FastAPI**, как вы прочли выше. + +### `ORJSONResponse` + +Быстрая альтернатива JSON-ответа, использующая `orjson`, как вы прочли выше. + +/// info | Информация + +Для этого требуется установка `orjson`, например с помощью `pip install orjson`. + +/// + +### `UJSONResponse` + +Альтернативный JSON-ответ, использующий `ujson`. + +/// info | Информация + +Для этого требуется установка `ujson`, например с помощью `pip install ujson`. + +/// + +/// warning | Предупреждение + +`ujson` менее осторожна, чем встроенная реализация Python, в том, как она обрабатывает некоторые крайние случаи. + +/// + +{* ../../docs_src/custom_response/tutorial001.py hl[2,7] *} + +/// tip | Совет + +Возможно, `ORJSONResponse` может быть более быстрой альтернативой. + +/// + +### `RedirectResponse` + +Возвращает HTTP-перенаправление. Использует статус-код 307 (Временное перенаправление) по умолчанию. + +Вы можете вернуть `RedirectResponse` напрямую: + +{* ../../docs_src/custom_response/tutorial006.py hl[2,9] *} + +--- + +Или вы можете использовать его в параметре `response_class`: + + +{* ../../docs_src/custom_response/tutorial006b.py hl[2,7,9] *} + +Если вы сделаете это, то можете вернуть URL непосредственно из вашей *функции-обработчика пути*. + +В этом случае, используемый `status_code` будет тем, что по умолчанию для `RedirectResponse`, а именно `307`. + +--- + +Вы также можете использовать параметр `status_code` в сочетании с параметром `response_class`: + +{* ../../docs_src/custom_response/tutorial006c.py hl[2,7,9] *} + +### `StreamingResponse` + +Принимает асинхронный генератор или обычный генератор/итератор и отправляет тело ответа в потоке. + +{* ../../docs_src/custom_response/tutorial007.py hl[2,14] *} + +#### Использование `StreamingResponse` с объектами, похожими на файлы + +Если у вас есть объект, похожий на файл (например, объект, возвращаемый `open()`), вы можете создать функцию-генератор для итерации по этому объекту. + +Таким образом, вам не нужно предварительно загружать все в память, и вы можете передать эту функцию-генератор в `StreamingResponse` и вернуть ее. + +Это включает множество библиотек для взаимодействия с облачным хранилищем, обработки видео и других. + +{* ../../docs_src/custom_response/tutorial008.py hl[2,10:12,14] *} + +1. Это функция-генератор. Это "функция-генератор", потому что она содержит оператор `yield`. +2. Используя блок `with`, мы обеспечиваем закрытие объекта, похожего на файл, после завершения выполнения функции-генератора. То есть после того, как отправка ответа завершена. +3. Этот `yield from` указывает функции итерироваться по объекту `file_like`. Затем для каждой части, которую итерация проходит, возвращать эту часть из функции-генератора (`iterfile`). + + Таким образом, это функция-генератор, которая передает работу по "генерации" чему-то еще во внутренности. + + Делая так, мы можем поместить это в блок `with` и таким образом обеспечить закрытие объекта, похожего на файл, после завершения. + +/// tip | Совет + +Обратите внимание, что здесь, так как мы используем стандартный `open()`, который не поддерживает `async` и `await`, мы объявляем операцию пути с использованием обычного `def`. + +/// + +### `FileResponse` + +Асинхронно отправляет файл в поток как ответ. + +Принимает другой набор аргументов для создания экземпляра, чем другие типы ответов: + +* `path` - Путь к файлу, который необходимо отправить в поток. +* `headers` - Любые пользовательские заголовки, которые необходимо включить, в виде словаря. +* `media_type` - Строка, задающая тип содержимого. Если не установлено, для определения типа содержимого будет использоваться имя файла или путь. +* `filename` - Если установлено, это будет включено в заголовок `Content-Disposition` ответа. + +Ответы на файлы будут включать соответствующие заголовки `Content-Length`, `Last-Modified` и `ETag`. + +{* ../../docs_src/custom_response/tutorial009.py hl[2,10] *} + +Вы также можете использовать параметр `response_class`: + +{* ../../docs_src/custom_response/tutorial009b.py hl[2,8,10] *} + +В этом случае, вы можете вернуть путь к файлу напрямую из вашей *функции-обработчика пути*. + +## Класс пользовательского ответа + +Вы можете создать свой собственный класс пользовательского ответа, наследуя от `Response` и используя его. + +Например, предположим, вы хотите использовать `orjson`, но с некоторыми пользовательскими настройками, не используемыми в включенном классе `ORJSONResponse`. + +Допустим, вы хотите, чтобы он возвращал JSON с отступами и форматированием, поэтому вы хотите использовать опцию orjson `orjson.OPT_INDENT_2`. + +Вы могли бы создать `CustomORJSONResponse`. Главное, что вам нужно сделать, это создать метод `Response.render(content)`, который возвращает содержимое как `bytes`: + +{* ../../docs_src/custom_response/tutorial009c.py hl[9:14,17] *} + +Теперь вместо возвращения: + +```json +{"message": "Hello World"} +``` + +...этот ответ вернет: + +```json +{ + "message": "Hello World" +} +``` + +Конечно, вы, вероятно, найдете гораздо лучшие способы использовать это, чем форматирование JSON. 😉 + +## Класс ответа по умолчанию + +При создании экземпляра класса **FastAPI** или `APIRouter` вы можете указать, какой класс ответа использовать по умолчанию. + +Параметр, который это определяет, называется `default_response_class`. + +В приведенном ниже примере **FastAPI** будет использовать `ORJSONResponse` по умолчанию во всех *операциях пути* вместо `JSONResponse`. + +{* ../../docs_src/custom_response/tutorial010.py hl[2,4] *} + +/// tip | Совет + +Вы все еще можете переопределять `response_class` в *операциях пути*, как и раньше. + +/// + +## Дополнительная документация + +Вы также можете объявить тип содержимого и многие другие детали в OpenAPI, используя `responses`: [Дополнительные ответы в OpenAPI](additional-responses.md){.internal-link target=_blank}. diff --git a/docs/ru/docs/advanced/dataclasses.md b/docs/ru/docs/advanced/dataclasses.md new file mode 100644 index 000000000..b82469a09 --- /dev/null +++ b/docs/ru/docs/advanced/dataclasses.md @@ -0,0 +1,95 @@ +# Использование Dataclasses + +FastAPI построен на основе **Pydantic**, и я уже показывал вам, как использовать модели Pydantic для объявления HTTP-запросов и HTTP-ответов. + +Но FastAPI также поддерживает использование `dataclasses` таким же образом: + +{* ../../docs_src/dataclasses/tutorial001.py hl[1,7:12,19:20] *} + +Это по-прежнему поддерживается благодаря **Pydantic**, так как в нем есть внутренняя поддержка `dataclasses`. + +Итак, даже с приведенным выше кодом, который явно не использует Pydantic, FastAPI использует Pydantic для преобразования стандартных dataclasses в собственный вариант dataclasses от Pydantic. + +И, конечно же, он поддерживает то же самое: + +* валидация данных +* сериализация данных +* документация данных и т.д. + +Это работает так же, как и с моделями Pydantic. И это фактически достигается таким же образом, под капотом, с использованием Pydantic. + +/// info | Информация + +Имейте в виду, что dataclasses не могут выполнять все то, что могут делать модели Pydantic. + +Поэтому, возможно, вам все равно придется использовать модели Pydantic. + +Но если у вас есть много dataclasses, это отличный трюк, чтобы использовать их для создания веб-API с помощью FastAPI. 🤓 + +/// + +## Dataclasses в `response_model` + +Вы также можете использовать `dataclasses` в параметре `response_model`: + +{* ../../docs_src/dataclasses/tutorial002.py hl[1,7:13,19] *} + +Dataclass будет автоматически преобразован в Pydantic dataclass. + +Таким образом, его схема будет отображаться в интерфейсе документации API: + + + +## Dataclasses в Вложенных Структурах Данных + +Вы также можете комбинировать `dataclasses` с другими аннотациями типов для создания вложенных структур данных. + +В некоторых случаях, возможно, вам все равно придется использовать версию `dataclasses` от Pydantic. Например, если у вас возникли ошибки с автоматической генерацией документации API. + +В этом случае вы можете просто заменить стандартные `dataclasses` на `pydantic.dataclasses`, что является заменой "на лету": + +{* ../../docs_src/dataclasses/tutorial003.py hl[1,5,8:11,14:17,23:25,28] *} + +1. Мы по-прежнему импортируем `field` из стандартных `dataclasses`. + +2. `pydantic.dataclasses` является заменой "на лету" для `dataclasses`. + +3. Dataclass `Author` включает список dataclasses `Item`. + +4. Dataclass `Author` используется как параметр `response_model`. + +5. Вы можете использовать другие стандартные аннотации типов с dataclasses как HTTP-тело запроса. + + В этом случае это список dataclasses `Item`. + +6. Здесь мы возвращаем словарь, содержащий `items`, который является списком dataclasses. + + FastAPI по-прежнему способен сериализовать данные в JSON. + +7. Здесь `response_model` использует аннотацию типа список dataclasses `Author`. + + Снова вы можете комбинировать `dataclasses` с стандартными аннотациями типов. + +8. Обратите внимание, что эта *функция-обработчик пути* использует обычный `def` вместо `async def`. + + Как всегда, в FastAPI вы можете сочетать `def` и `async def` в зависимости от необходимости. + + Если вы забыли, когда использовать какой, посмотрите раздел _"Нет времени?"_ в документации о [`async` и `await`](../async.md#in-a-hurry){.internal-link target=_blank}. + +9. Эта *функция-обработчик пути* не возвращает dataclasses (хотя могла бы), а список словарей с внутренними данными. + + FastAPI будет использовать параметр `response_model` (который включает dataclasses), чтобы преобразовать ответ. + +Вы можете комбинировать `dataclasses` с другими аннотациями типов в различных комбинациях, чтобы формировать сложные структуры данных. + +Посмотрите советы по аннотации в коде выше, чтобы узнать больше деталей. + +## Узнать больше + +Вы также можете комбинировать `dataclasses` с другими моделями Pydantic, наследовать их, включать их в свои собственные модели и т.д. + +Чтобы узнать больше, ознакомьтесь с документацией Pydantic о dataclasses. + +## Версия + +Это доступно начиная с версии FastAPI `0.67.0`. 🔖 diff --git a/docs/ru/docs/advanced/events.md b/docs/ru/docs/advanced/events.md new file mode 100644 index 000000000..ac5b8affd --- /dev/null +++ b/docs/ru/docs/advanced/events.md @@ -0,0 +1,165 @@ +# События жизненного цикла + +Вы можете определить логику (код), которая должна быть выполнена перед запуском приложения. Это значит, что данный код будет выполнен **единожды**, **перед** тем, как приложение **начнет принимать HTTP-запросы**. + +Аналогично, вы можете определить логику (код), которая должна быть выполнена при остановке приложения. В этом случае код будет выполнен **единожды**, **после** обработки, возможно, **множества HTTP-запросов**. + +Поскольку этот код выполняется до того, как приложение **начинает** принимать запросы, и непосредственно после того, как оно **заканчивает** обработку запросов, он охватывает весь **lifespan** приложения (слово "lifespan" сейчас станет важным 😉). + +Это может быть очень полезно для настройки **ресурсов**, которые вам нужно использовать для всего приложения, и которые **разделяются** между запросами, и/или которые требуют **освобождения** после завершения работы. Например, пул подключений к базе данных или загрузка общей модели машинного обучения. + +## Пример использования + +Давайте начнем с примера **использования**, а затем посмотрим, как его решить. + +Предположим, у вас есть несколько **моделей машинного обучения**, которые вы хотите использовать для обработки запросов. 🤖 + +Те же самые модели разделяются между всеми запросами, то есть это не одна модель на запрос или одна на пользователя или что-то подобное. + +Представим, что загрузка модели может **занять значительное время**, потому что необходимо прочитать много **данных с диска**. Поэтому вы не хотите делать это для каждого запроса. + +Вы можете загрузить ее на верхнем уровне модуля/файла, но это также означало бы, что модель **загрузится**, даже если вы просто запускаете простой автоматизированный тест, и такой тест был бы **медленный**, так как пришлось бы ждать, пока модель загрузится, прежде чем можно было бы выполнить независимую часть кода. + +Это то, что мы решаем: давайте загрузим модель до того, как запросы начнут обрабатываться, но только непосредственно перед тем, как приложение начнет принимать запросы, а не в процессе загрузки кода. + +## Lifespan + +Вы можете определить эту логику *запуска* и *выключения* с помощью параметра `lifespan` в приложении `FastAPI` и "менеджера контекста" (я покажу вам, что это такое, через мгновение). + +Давайте начнем с примера и затем разберем его подробно. + +Создаем асинхронную функцию `lifespan()` с `yield` следующим образом: + +{* ../../docs_src/events/tutorial003.py hl[16,19] *} + +Здесь мы симулируем дорогостоящую операцию *запуска*, загружая модель, помещая (фальшивую) функцию модели в словарь с моделями машинного обучения перед `yield`. Этот код будет выполнен **до** того, как приложение **начнет принимать запросы**, в процессе *запуска*. + +И затем, сразу после `yield`, мы выгружаем модель. Этот код будет выполнен **после** того, как приложение **завершит обработку запросов**, непосредственно перед *выключением*. Это может, например, освободить такие ресурсы, как память или GPU. + +/// tip | Совет + +Выключение происходит, когда вы **останавливаете** приложение. + +Возможно, вам нужно запустить новую версию, или вы просто устали от его работы. 🤷 + +/// + +### Функция Lifespan + +Первое, что следует заметить, это то, что мы определяем асинхронную функцию с `yield`. Это очень похоже на зависимости с `yield`. + +{* ../../docs_src/events/tutorial003.py hl[14:19] *} + +Первая часть функции, до `yield`, будет выполнена **до** того, как приложение начнет свою работу. + +А часть после `yield` будет выполнена **после** завершения работы приложения. + +### Асинхронный менеджер контекста + +Если вы посмотрите, функция декорирована с помощью `@asynccontextmanager`. + +Это преобразует функцию в нечто, называемое "**асинхронным менеджером контекста**". + +{* ../../docs_src/events/tutorial003.py hl[1,13] *} + +**Менеджер контекста** в Python - это нечто, что вы можете использовать в операторе `with`, например, `open()` может быть использован как менеджер контекста: + +```Python +with open("file.txt") as file: + file.read() +``` + +В последних версиях Python также существует **асинхронный менеджер контекста**. Вы бы использовали его с `async with`: + +```Python +async with lifespan(app): + await do_stuff() +``` + +Когда вы создаете менеджер контекста или асинхронный менеджер контекста, как выше, его работа заключается в том, что, до входа в блок `with`, он выполняет код перед `yield`, и после выхода из блока `with`, он выполняет код после `yield`. + +В нашем примере кода выше мы не используем его напрямую, но передаем его FastAPI для использования. + +Параметр `lifespan` приложения `FastAPI` принимает **асинхронный менеджер контекста**, так что мы можем передать ему наш новый асинхронный менеджер контекста `lifespan`. + +{* ../../docs_src/events/tutorial003.py hl[22] *} + +## Альтернативные события (устаревшие) + +/// warning | Предупреждение + +Рекомендуемый способ обработки *запуска* и *выключения* — это использование параметра `lifespan` приложения `FastAPI`, как описано выше. Если вы предоставите параметр `lifespan`, обработчики событий `startup` и `shutdown` больше не будут вызываться. Вы можете использовать либо `lifespan`, либо события, но не оба. + +Эту часть можно, вероятно, пропустить. + +/// + +Существует альтернативный способ определения логики, которая будет выполнена во время *запуска* и во время *выключения*. + +Вы можете определить обработчики событий (функции), которые необходимо выполнить до запуска приложения или при его остановке. + +Эти функции могут быть объявлены как `async def`, так и обычный `def`. + +### Событие `startup` + +Чтобы добавить функцию, которую следует запустить перед запуском приложения, объявите ее с событием `"startup"`: + +{* ../../docs_src/events/tutorial001.py hl[8] *} + +В этом случае функция-обработчик события `startup` инициализирует "базу данных" элементов (просто `dict`) с некоторыми значениями. + +Вы можете добавить более одной функции-обработчика события. + +И ваше приложение не начнет принимать запросы до тех пор, пока все обработчики событий `startup` не будут выполнены. + +### Событие `shutdown` + +Чтобы добавить функцию, которую следует запустить при остановке приложения, объявите ее с событием `"shutdown"`: + +{* ../../docs_src/events/tutorial002.py hl[6] *} + +Здесь функция-обработчик события `shutdown` запишет строку текста `"Application shutdown"` в файл `log.txt`. + +/// info | Информация + +В функции `open()`, `mode="a"` означает "добавить", так что строка будет добавлена после того, что уже есть в этом файле, без перезаписи предыдущего содержимого. + +/// + +/// tip | Совет + +Обратите внимание, что в данном случае мы используем стандартную функцию Python `open()`, которая взаимодействует с файлом. + +Таким образом, это включает в себя ввод/вывод (I/O), который требует "ожидания" для записи данных на диск. + +Но `open()` не использует `async` и `await`. + +Поэтому мы объявляем функцию-обработчик события как стандартный `def` вместо `async def`. + +/// + +### `startup` и `shutdown` вместе + +Существует большая вероятность, что логика ваших *запуска* и *выключения* связана; возможно, вы хотите что-то запустить, а затем завершить, получить ресурс, а затем освободить его и т. д. + +Делать это в раздельных функциях, которые не используют совместно логику или переменные, сложнее, так как придется сохранять значения в глобальных переменных или использовать подобные уловки. + +Из-за этого теперь рекомендуется вместо этого использовать `lifespan`, как объяснено выше. + +## Технические детали + +Просто техническая деталь для любознательных гиков. 🤓 + +В основе, в технической спецификации ASGI, это часть Протокола Lifespan, и он определяет события, называемые `startup` и `shutdown`. + +/// info | Информация + +Вы можете прочитать больше об обработчиках `lifespan` в Starlette в документации "Lifespan" Starlette. + +Включая информацию о том, как обрабатывать состояние lifespan, которое может быть использовано в других частях вашего кода. + +/// + +## Подприложения + +🚨 Помните, что эти события жизненного цикла (запуска и завершения) будут выполнены только для основного приложения, а не для [подприложений - монтировок](sub-applications.md){.internal-link target=_blank}. diff --git a/docs/ru/docs/advanced/generate-clients.md b/docs/ru/docs/advanced/generate-clients.md new file mode 100644 index 000000000..7e8e274c1 --- /dev/null +++ b/docs/ru/docs/advanced/generate-clients.md @@ -0,0 +1,261 @@ +# Генерация клиентов + +Так как **FastAPI** основан на спецификации OpenAPI, вы автоматически получаете совместимость со многими инструментами, включая автоматическую документацию API (предоставляемую Swagger UI). + +Один конкретный плюс, который не всегда очевиден, заключается в том, что вы можете **сгенерировать клиентов** (иногда их называют **SDKs** ) для вашего API для многих разных **языков программирования**. + +## Генераторы клиентов OpenAPI + +Существует множество инструментов для генерации клиентов из **OpenAPI**. + +Один из популярных инструментов — это OpenAPI Generator. + +Если вы создаете **фронтенд**, очень интересной альтернативой является openapi-ts. + +## Генераторы клиентов и SDK - спонсоры + +Существуют также некоторые **поддерживаемые компаниями** генераторы клиентов и SDK, основанные на OpenAPI (FastAPI), в некоторых случаях они могут предложить вам **дополнительные функции** поверх высококачественных сгенерированных SDK/клиентов. + +Некоторые из них также ✨ [**спонсируют FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, что обеспечивает продолжительное и здоровое **развитие** FastAPI и его **экосистемы**. + +Это также демонстрирует их истинную приверженность FastAPI и его **сообществу** (вам), ведь они не только хотят предоставить вам **качественное обслуживание**, но и стремятся обеспечить вам **качественный и здоровый фреймворк**, FastAPI. 🙇 + +Например, вам может быть интересно попробовать: + +* Speakeasy +* Stainless +* liblab + +Также существует несколько других компаний, предлагающих аналогичные услуги, которые вы можете найти в интернете. 🤓 + +## Генерация TypeScript-клиента для фронтенда + +Давайте начнем с простой FastAPI-программы: + +{* ../../docs_src/generate_clients/tutorial001_py39.py hl[7:9,12:13,16:17,21] *} + +Обратите внимание, что *операции пути* определяют модели, которые они используют для полезной нагрузки запроса и ответа, используя модели `Item` и `ResponseMessage`. + +### Документация API + +Если вы перейдете к документации API, вы увидите, что в ней содержатся **схемы** для данных, которые должны быть отправлены в запросах и получены в ответах: + + + +Вы видите эти схемы, потому что они были объявлены с моделями в приложении. + +Эта информация доступна в **схеме OpenAPI** приложения и затем отображается в документации API (с помощью Swagger UI). + +И эта же информация из моделей, включенных в OpenAPI, может быть использована для **генерации кода клиента**. + +### Генерация TypeScript-клиента + +Теперь, когда у нас есть приложение с моделями, мы можем сгенерировать код клиента для фронтенда. + +#### Установка `openapi-ts` + +Вы можете установить `openapi-ts` в код своего фронтенда с помощью: + +
+ +```console +$ npm install @hey-api/openapi-ts --save-dev + +---> 100% +``` + +
+ +#### Генерация кода клиента + +Чтобы сгенерировать код клиента, вы можете использовать командное приложение `openapi-ts`, которое теперь будет установлено. + +Поскольку оно установлено в локальном проекте, вы, вероятно, не сможете вызвать эту команду напрямую, но вы можете поместить её в свой файл `package.json`. + +Он может выглядеть следующим образом: + +```JSON hl_lines="7" +{ + "name": "frontend-app", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "generate-client": "openapi-ts --input http://localhost:8000/openapi.json --output ./src/client --client axios" + }, + "author": "", + "license": "", + "devDependencies": { + "@hey-api/openapi-ts": "^0.27.38", + "typescript": "^4.6.2" + } +} +``` + +После добавления этого скрипта NPM `generate-client` вы можете запустить его с помощью: + +
+ +```console +$ npm run generate-client + +frontend-app@1.0.0 generate-client /home/user/code/frontend-app +> openapi-ts --input http://localhost:8000/openapi.json --output ./src/client --client axios +``` + +
+ +Эта команда сгенерирует код в `./src/client` и будет использовать `axios` (фронтенд HTTP-библиотеку) для внутренних нужд. + +### Попробуйте код клиента + +Теперь вы можете импортировать и использовать код клиента, он может выглядеть следующим образом, обратите внимание, что вы получаете автозавершение для методов: + + + +Вы также получите автозавершение для отправляемой полезной нагрузки: + + + +/// tip | Совет + +Обратите внимание на автозавершение для `name` и `price`, которые были определены в приложении FastAPI, в модели `Item`. + +/// + +Вы получите ошибки на лету для данных, которые вы отправляете: + + + +Объект-ответ также будет иметь автозавершение: + + + +## FastAPI-приложение с тегами + +Во многих случаях ваше FastAPI-приложение будет больше, и вы, скорее всего, будете использовать теги для разделения разных групп *операций пути*. + +Например, у вас может быть раздел для **товаров** и другой раздел для **пользователей**, и они могут быть разделены с помощью тегов: + +{* ../../docs_src/generate_clients/tutorial002_py39.py hl[21,26,34] *} + +### Генерация TypeScript-клиента с тегами + +Если вы генерируете клиента для FastAPI-приложения с использованем тегов, то обычно клиентский код также будет разделен по тегам. + +Таким образом, вы сможете правильно упорядочить и сгруппировать клиентский код: + + + +В данном случае у вас есть: + +* `ItemsService` +* `UsersService` + +### Имена методов клиента + +Сейчас сгенерированные имена методов, такие как `createItemItemsPost`, не выглядят очень чистыми: + +```TypeScript +ItemsService.createItemItemsPost({name: "Plumbus", price: 5}) +``` + +...это потому, что генератор клиента использует внутренний **идентификатор операции** OpenAPI для каждой *операции пути*. + +OpenAPI требует, чтобы каждый идентификатор операции был уникальным среди всех *операций пути*, поэтому FastAPI использует **название функции**, **путь** и **метод/операцию HTTP** для генерации этого идентификатора операции, поскольку таким образом он может обеспечить уникальность идентификаторов операций. + +Но я покажу вам, как улучшить это. 🤓 + +## Пользовательские идентификаторы операций и лучшие имена методов + +Вы можете **изменить** способ **генерации** этих идентификаторов операций, чтобы упростить их и получить **более простые названия методов** в клиентах. + +В этом случае вы должны обеспечить уникальность каждого идентификатора операции другим способом. + +Например, вы можете убедиться, что каждая *операция пути* имеет тег, а затем генерировать идентификатор операции на основе **тега** и **названия операции пути** (названия функции). + +### Пользовательская функция генерации уникального идентификатора + +FastAPI использует **уникальный ID** для каждой *операции пути*, он используется для **идентификатора операции**, а также для имен любых необходимых пользовательских моделей для запросов или ответов. + +Вы можете настроить эту функцию. Она принимает `APIRoute` и возвращает строку. + +Например, здесь используется первый тег (обычно у вас будет только один тег) и название *операции пути* (название функции). + +Вы можете передать эту пользовательскую функцию **FastAPI** в параметре `generate_unique_id_function`: + +{* ../../docs_src/generate_clients/tutorial003_py39.py hl[6:7,10] *} + +### Генерация TypeScript-клиента с пользовательскими идентификаторами операций + +Теперь, если вы снова сгенерируете клиент, вы увидите, что у него улучшенные названия методов: + + + +Как видите, названия методов теперь содержат тег, а затем имя функции, теперь они не включают информацию из URL-пути и HTTP-операции. + +### Предобработка спецификации OpenAPI для генератора клиентов + +Сгенерированный код все равно содержит некоторую **дублирующую информацию**. + +Мы уже знаем, что этот метод связан с **товарами (items)**, потому что это слово появилось в `ItemsService` (взято из тега), но у нас все равно есть имя тега в имени метода. 😕 + +Вероятно, мы все равно захотим сохранить это для OpenAPI в целом, так как это обеспечит **уникальность** идентификаторов операций. + +Но для сгенерированного клиента мы можем **изменить** идентификаторы операций OpenAPI прямо перед генерацией клиентов, только чтобы сделать эти названия методов более приятными и **чистыми**. + +Мы можем загрузить OpenAPI JSON в файл `openapi.json`, а затем **удалить** этот префикс тега с помощью такого скрипта: + +{* ../../docs_src/generate_clients/tutorial004.py *} + +//// tab | Node.js + +```Javascript +{!> ../../docs_src/generate_clients/tutorial004.js!} +``` + +//// + +Таким образом, идентификаторы операций будут переименованы из `items-get_items` в просто `get_items`, так генератор клиентов сможет генерировать более простые имена методов. + +### Генерация TypeScript-клиента с предобработанным OpenAPI + +Теперь, так как конечный результат находится в файле `openapi.json`, вы измените файл `package.json`, чтобы использовать этот локальный файл, например: + +```JSON hl_lines="7" +{ + "name": "frontend-app", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "generate-client": "openapi-ts --input ./openapi.json --output ./src/client --client axios" + }, + "author": "", + "license": "", + "devDependencies": { + "@hey-api/openapi-ts": "^0.27.38", + "typescript": "^4.6.2" + } +} +``` + +После генерации нового клиента у вас теперь будут **чистые названия методов**, с всеми **подсказками автозавершения**, **ошибками на лету** и т.д.: + + + +## Преимущества + +При использовании автоматически сгенерированных клиентов вы получите **автозавершение** для: + +* Методов. +* Полезной нагрузки запроса в теле, параметрах запроса и т.д. +* Полезной нагрузки ответа. + +Вы также получите **ошибки на лету** для всего. + +И всякий раз, когда вы обновляете код на серверной стороне и **регенерируете** фронтенд, он будет содержать любые новые *операции пути* как методы, старые будут удалены, а любые другие изменения будут отражены в сгенерированном коде. 🤓 + +Это также означает, что если что-то изменится, это будет **отражено** в клиентском коде автоматически. И если вы **соберёте** клиент, у него возникнет ошибка, если у вас возникнет **несоответствие** в используемых данных. + +Таким образом, вы **обнаружите множество ошибок** на раннем этапе цикла разработки, вместо того чтобы ждать, пока ошибки проявятся у ваших конечных пользователей в продакшне, и потом пытаться выяснить, в чем проблема. ✨ diff --git a/docs/ru/docs/advanced/middleware.md b/docs/ru/docs/advanced/middleware.md new file mode 100644 index 000000000..e2f1a8ba1 --- /dev/null +++ b/docs/ru/docs/advanced/middleware.md @@ -0,0 +1,96 @@ +# Продвинутое использование Middleware + +В основном руководстве вы узнали, как добавить [пользовательский Middleware](../tutorial/middleware.md){.internal-link target=_blank} в ваше приложение. + +А также вы узнали, как обрабатывать [CORS с использованием `CORSMiddleware`](../tutorial/cors.md){.internal-link target=_blank}. + +В этом разделе мы увидим, как использовать другие middleware. + +## Добавление ASGI middleware + +Так как **FastAPI** основан на Starlette и реализует спецификацию ASGI, вы можете использовать любой ASGI middleware. + +Для работы middleware не обязательно должны быть созданы специально для FastAPI или Starlette, достаточно того, чтобы они следовали спецификации ASGI. + +В общем случае, ASGI middleware представляют собой классы, которые ожидают получение ASGI приложения в качестве первого аргумента. + +Таким образом, в документации для сторонних ASGI middleware, скорее всего, будет указано сделать следующее: + +```Python +from unicorn import UnicornMiddleware + +app = SomeASGIApp() + +new_app = UnicornMiddleware(app, some_config="rainbow") +``` + +Но FastAPI (на самом деле Starlette) предоставляет более простой способ сделать это, обеспечивая правильную обработку ошибок сервера и работу пользовательских обработчиков ошибок. + +Для этого используйте `app.add_middleware()` (как в примере для CORS). + +```Python +from fastapi import FastAPI +from unicorn import UnicornMiddleware + +app = FastAPI() + +app.add_middleware(UnicornMiddleware, some_config="rainbow") +``` + +`app.add_middleware()` принимает класс middleware в качестве первого аргумента и любые дополнительные аргументы, которые должны быть переданы в middleware. + +## Интегрированные middleware + +**FastAPI** включает несколько middleware для общих случаев использования, далее мы рассмотрим, как их использовать. + +/// note | Технические детали + +В следующих примерах вы также можете использовать `from starlette.middleware.something import SomethingMiddleware`. + +**FastAPI** предоставляет несколько middleware в `fastapi.middleware` исключительно для удобства разработчика. Но большинство доступных middleware поступает непосредственно из Starlette. + +/// + +## `HTTPSRedirectMiddleware` + +Обеспечивает, чтобы все входящие запросы были перенаправлены на `https` или `wss`. + +Любой входящий запрос на `http` или `ws` будет перенаправлен на защищенную схему. + +{* ../../docs_src/advanced_middleware/tutorial001.py hl[2,6] *} + +## `TrustedHostMiddleware` + +Обеспечивает наличие корректно заданного HTTP-заголовка `Host` во всех входящих запросах для защиты от атак на основе HTTP-заголовка Host. + +{* ../../docs_src/advanced_middleware/tutorial002.py hl[2,6:8] *} + +Поддерживаются следующие аргументы: + +* `allowed_hosts` - список доменных имен, которые разрешены в качестве имен хостов. Разрешены шаблоны доменов, такие как `*.example.com`, для соответствия поддоменам. Для разрешения всех имен хостов можно использовать `allowed_hosts=["*"]` или вовсе не добавлять middleware. + +Если входящий запрос не валидируется, отправляется ответ с кодом `400`. + +## `GZipMiddleware` + +Обрабатывает GZip-ответы для любого запроса, который включает `"gzip"` в HTTP-заголовке `Accept-Encoding`. + +Middleware будет обрабатывать как стандартные, так и потоковые HTTP-ответы. + +{* ../../docs_src/advanced_middleware/tutorial003.py hl[2,6] *} + +Поддерживаются следующие аргументы: + +* `minimum_size` - не выполнять GZip сжатие для HTTP-ответов, которые меньше этого минимального размера в байтах. По умолчанию `500`. +* `compresslevel` - используется во время GZip сжатия. Это число в диапазоне от 1 до 9. По умолчанию `9`. Меньшее значение приводит к более быстрому сжатию, но большему размеру файлов, в то время как большее значение приводит к более медленному сжатию, но меньшему размеру файлов. + +## Другие middleware + +Существует много других ASGI middleware. + +Например: + +* `ProxyHeadersMiddleware` от Uvicorn +* MessagePack + +Чтобы увидеть другие доступные middleware, ознакомьтесь с документацией Starlette по Middleware и списком ASGI Awesome List. diff --git a/docs/ru/docs/advanced/openapi-callbacks.md b/docs/ru/docs/advanced/openapi-callbacks.md new file mode 100644 index 000000000..3de7f3610 --- /dev/null +++ b/docs/ru/docs/advanced/openapi-callbacks.md @@ -0,0 +1,186 @@ +# OpenAPI Callbacks + +Вы можете создать API с *операцией пути*, которая может инициировать запрос к *внешнему API*, созданному кем-то другим (вероятно, тем же разработчиком, который будет *использовать* ваше API). + +Процесс, который происходит, когда ваше API-приложение вызывает *внешний API*, называется "callback". Потому что ПО, написанное внешним разработчиком, отправляет запрос вашему API, а затем ваше API *отвечает*, отправляя запрос к *внешнему API* (который, возможно, был создан тем же разработчиком). + +В этом случае, вы могли бы захотеть задокументировать, как этот внешний API *должен* выглядеть. Какие *операции пути* он должен содержать, какое тело ожидать, какой ответ должен возвращать и т.д. + +## Приложение с callback'ами + +Рассмотрим это на примере. + +Представьте, что вы разрабатываете приложение, позволяющее создавать счета. + +Эти счета будут иметь `id`, `title` (опционально), `customer` и `total`. + +Пользователь вашего API (внешний разработчик) создаст счет в вашем API с помощью POST-запроса. + +Затем ваше API будет (давайте представим): + +* Отправлять счет какому-то клиенту внешнего разработчика. +* Собирать деньги. +* Отправлять уведомление обратно пользователю API (внешнему разработчику). + * Это будет сделано с помощью отправки POST-запроса (от *вашего API*) в некоторый *внешний API*, предоставленный этим внешним разработчиком (это и есть "callback"). + +## Обычное приложение **FastAPI** + +Сначала посмотрим, как обычное приложение API выглядело бы до добавления callback'а. + +Оно будет иметь *операцию пути*, которая будет принимать `Invoice` в теле запроса и параметр запроса `callback_url`, который будет содержать URL для callback'а. + +Эта часть довольно обычная, и, вероятно, большая часть кода уже знакома вам: + +{* ../../docs_src/openapi_callbacks/tutorial001.py hl[9:13,36:53] *} + +/// tip | Подсказка + +Параметр запроса `callback_url` использует тип Url из Pydantic Url. + +/// + +Единственное новое здесь — это `callbacks=invoices_callback_router.routes` как аргумент к *декоратору операции пути*. Дальше мы увидим, что это значит. + +## Документирование callback'а + +Фактический код callback'а будет сильно зависеть от вашего API-приложения. + +И, вероятно, значительно изменится от одного приложения к другому. + +Это может быть всего одна или две строчки кода, такие как: + +```Python +callback_url = "https://example.com/api/v1/invoices/events/" +httpx.post(callback_url, json={"description": "Invoice paid", "paid": True}) +``` + +Но, возможно, наиболее важная часть callback'а — это убедиться, что пользователь вашего API (внешний разработчик) корректно реализует *внешний API*, согласно данным, которые *ваше API* собирается отправить в теле запроса callback'а и т.д. + +Итак, следующее, что мы сделаем, это добавим код для документации, как этот *внешний API* должен выглядеть для получения callback'а от *вашего API*. + +Эта документация отобразится в интерфейсе Swagger по адресу `/docs` в вашем API и позволит внешним разработчикам узнать, как создать *внешний API*. + +Этот пример не реализует сам callback (это может быть всего одна строка кода), только часть документации. + +/// tip | Подсказка + +Фактический callback — это просто HTTP-запрос. + +При реализации callback'а самостоятельно, вы можете использовать что-то вроде HTTPX или Requests. + +/// + +## Написание кода документации для callback'а + +Этот код не будет выполнен в вашем приложении, нам нужно его только чтобы *документировать*, как *внешний API* должен выглядеть. + +Но вы уже знаете, как легко создавать автоматическую документацию для API с **FastAPI**. + +Поэтому мы используем эти же знания, чтобы задокументировать, как *внешний API* должен выглядеть... создавая *операцию(ии) пути*, которые внешний API должен реализовать (те, которые ваше API вызовет). + +/// tip | Подсказка + +Когда пишете код для документации callback'а, может быть полезно представить, что вы — этот *внешний разработчик*. И что вы в данный момент реализуете *внешний API*, а не *ваше API*. + +Временное принятие этой точки зрения (внешнего разработчика) поможет вам скорее определить, куда помещать параметры, Pydantic-модель для тела запроса, для ответа и т.д. для этого *внешнего API*. + +/// + +### Создание callback `APIRouter` + +Сначала создайте новый `APIRouter`, который будет содержать один или несколько callback'ов. + +{* ../../docs_src/openapi_callbacks/tutorial001.py hl[3,25] *} + +### Создание callback *операции пути* + +Чтобы создать callback *операцию пути*, используйте тот же `APIRouter`, который вы создали выше. + +Она должна выглядеть как обычная операция пути FastAPI: + +* Она, вероятно, должна иметь объявление тела запроса, которое она должна принять, например `body: InvoiceEvent`. +* И она может также иметь объявление ответа, который она должна вернуть, например `response_model=InvoiceEventReceived`. + +{* ../../docs_src/openapi_callbacks/tutorial001.py hl[16:18,21:22,28:32] *} + +Есть 2 основных отличия от обычной *операции пути*: + +* Реальный код не нужен, так как ваше приложение никогда не вызовет этот код. Он используется только для документирования *внешнего API*. Поэтому функция может просто содержать `pass`. +* В *пути* может содержаться выражение OpenAPI 3 (подробнее ниже), где можно использовать переменные с параметрами и частями оригинального запроса, отправленного к *вашему API*. + +### Выражение пути callback'а + +Путь callback'а может содержать выражение OpenAPI 3, которое может включать части оригинального запроса, отправленного к *вашему API*. + +В этом случае это `str`: + +```Python +"{$callback_url}/invoices/{$request.body.id}" +``` + +Таким образом, если пользователь вашего API (внешний разработчик) отправляет запрос к *вашему API* по адресу: + +``` +https://yourapi.com/invoices/?callback_url=https://www.external.org/events +``` + +с JSON-телом: + +```JSON +{ + "id": "2expen51ve", + "customer": "Mr. Richie Rich", + "total": "9999" +} +``` + +тогда *ваше API* обработает счет, и чуть позже отправит callback-запрос на `callback_url` (во *внешний API*): + +``` +https://www.external.org/events/invoices/2expen51ve +``` + +с JSON-телом, содержащим что-то вроде: + +```JSON +{ + "description": "Payment celebration", + "paid": true +} +``` + +и ожидает ответа от этого *внешнего API* с JSON-телом вроде: + +```JSON +{ + "ok": true +} +``` + +/// tip | Подсказка + +Обратите внимание, как callback URL включает URL, полученный как параметр запроса в `callback_url` (`https://www.external.org/events`), и также `id` счета из тела JSON (`2expen51ve`). + +/// + +### Добавление router для callback'ов + +На данном этапе у вас есть *операция(ии) пути для callback'а*, необходимые (те, которые *внешний разработчик* должен реализовать во *внешнем API*) в callback router, который вы создали выше. + +Теперь используйте параметр `callbacks` в *декораторе операции пути вашего API*, чтобы передать атрибут `.routes` (который на самом деле является просто `list`'ом маршрутов/*операций пути*) из этого callback router: + +{* ../../docs_src/openapi_callbacks/tutorial001.py hl[35] *} + +/// tip | Подсказка + +Обратите внимание, что вы не передаете сам router (`invoices_callback_router`) в `callback=`, а передаете атрибут `.routes`, как в `invoices_callback_router.routes`. + +/// + +### Проверка документации + +Теперь вы можете запустить ваше приложение и перейти на http://127.0.0.1:8000/docs. + +Вы увидите вашу документацию, включая раздел "Callbacks" для вашей *операции пути*, который показывает, как должен выглядеть *внешний API*: + + diff --git a/docs/ru/docs/advanced/openapi-webhooks.md b/docs/ru/docs/advanced/openapi-webhooks.md new file mode 100644 index 000000000..4854bd513 --- /dev/null +++ b/docs/ru/docs/advanced/openapi-webhooks.md @@ -0,0 +1,55 @@ +# OpenAPI Webhooks + +Существуют случаи, когда вы хотите сообщить пользователям вашего API, что ваше приложение может отправлять запросы их приложениям с данными, обычно для уведомления о каком-то событии. + +Это означает, что вместо обычного процесса, когда ваши пользователи отправляют запросы вашему API, это ваш API (или ваше приложение) может отправлять запросы их системе (их API, их приложению). + +Это обычно называется **webhook**. + +## Шаги вебхуков + +Процесс обычно состоит в том, что **вы определяете** в вашем коде сообщение, которое вы собираетесь отправить, **тело запроса**. + +Вы также каким-либо образом определяете, в какие **моменты** ваше приложение будет отправлять эти запросы или события. + +И **ваши пользователи** каким-либо образом (например, в веб-панели управления) определяют **URL**, куда ваше приложение должно отправлять эти запросы. + +Вся **логика** регистрации URL-адресов для вебхуков и кода для фактической отправки этих запросов зависит от вас. Вы пишете это в **собственном коде** так, как хотите. + +## Документирование вебхуков с помощью **FastAPI** и OpenAPI + +С помощью **FastAPI** и OpenAPI вы можете определить названия этих вебхуков, типы HTTP операций, которые ваше приложение может отправлять (например, `POST`, `PUT` и т.д.), и **тела запросов**, которые ваше приложение будет отправлять. + +Это может значительно упростить вашим пользователям **внедрение их API** для получения ваших **вебхуков**, они даже могут автоматически сгенерировать часть своего API кода. + +/// info | Информация + +Вебхуки доступны в OpenAPI 3.1.0 и выше, поддерживаемые FastAPI `0.99.0` и выше. + +/// + +## Приложение с вебхуками + +Когда вы создаете приложение **FastAPI**, есть атрибут `webhooks`, который вы можете использовать для определения *вебхуков* так же, как вы бы определяли *операции пути*, например с помощью `@app.webhooks.post()`. + +{* ../../docs_src/openapi_webhooks/tutorial001.py hl[9:13,36:53] *} + +Вебхуки, которые вы определяете, попадут в **схему OpenAPI** и в **автоматическую документацию UI**. + +/// info | Информация + +Объект `app.webhooks` на самом деле является `APIRouter`, того же типа, который вы бы использовали при структурировании своего приложения с множеством файлов. + +/// + +Обратите внимание, что с вебхуками вы на самом деле не объявляете *путь* (например, `/items/`), текст, который вы передаете, является просто **идентификатором** вебхука (название события), например в `@app.webhooks.post("new-subscription")`, имя вебхука — `new-subscription`. + +Это потому, что ожидается, что **ваши пользователи** определят фактический **путь URL**, куда они хотят получать запрос webhook, каким-то другим образом (например, в веб-панели управления). + +### Проверьте документацию + +Теперь вы можете запустить ваше приложение и перейти по адресу http://127.0.0.1:8000/docs. + +Вы увидите, что ваша документация содержит обычные *операции пути* и теперь также некоторые **вебхуки**: + + diff --git a/docs/ru/docs/advanced/path-operation-advanced-configuration.md b/docs/ru/docs/advanced/path-operation-advanced-configuration.md new file mode 100644 index 000000000..057d14952 --- /dev/null +++ b/docs/ru/docs/advanced/path-operation-advanced-configuration.md @@ -0,0 +1,204 @@ +# Продвинутая конфигурация операций пути + +## OpenAPI operationId + +/// warning | Предупреждение + +Если вы не "эксперт" в OpenAPI, то, скорее всего, вам это не понадобится. + +/// + +Вы можете задать `operationId` для OpenAPI, который будет использоваться в вашей *операции пути* с помощью параметра `operation_id`. + +Следует удостовериться, что он уникален для каждой операции. + +{* ../../docs_src/path_operation_advanced_configuration/tutorial001.py hl[6] *} + +### Использование имени функции *операции пути* в качестве operationId + +Если вы хотите использовать имена ваших функций API в качестве `operationId`, вы можете перебрать их все и переопределить `operation_id` каждой *операции пути*, используя `APIRoute.name`. + +Необходимо сделать это после добавления всех ваших *операций пути*. + +{* ../../docs_src/path_operation_advanced_configuration/tutorial002.py hl[2, 12:21, 24] *} + +/// tip | Совет + +Если вы вручную вызываете `app.openapi()`, обновите `operationId` до этого. + +/// + +/// warning | Предупреждение + +Если вы это делаете, необходимо удостовериться, что каждая из ваших *функций операций пути* имеет уникальное имя. + +Даже если они находятся в разных модулях (файлах Python). + +/// + +## Исключение из OpenAPI + +Чтобы исключить *операцию пути* из генерируемой схемы OpenAPI (и таким образом, из систем автоматической документации), используйте параметр `include_in_schema` и установите его значение как `False`: + +{* ../../docs_src/path_operation_advanced_configuration/tutorial003.py hl[6] *} + +## Расширенное описание из строки документации + +Вы можете ограничить количество строк, используемых из строки документации функции *операции пути* для OpenAPI. + +Добавление `\f` (экранированный символ "перевод страницы") приведет к тому, что **FastAPI** обрежет вывод, используемый для OpenAPI, на этом месте. + +Этот символ не покажется в документации, но другие инструменты (такие как Sphinx) смогут использовать остальную часть. + +{* ../../docs_src/path_operation_advanced_configuration/tutorial004.py hl[19:29] *} + +## Дополнительные ответы + +Вы, вероятно, уже знаете, как объявить `response_model` и `status_code` для *операции пути*. + +Это определяет метаданные об основном ответе *операции пути*. + +Вы также можете объявить дополнительные ответы с их моделями, статус-кодами и т.д. + +В документации есть целая глава об этом, которую вы можете прочитать в разделе [Дополнительные ответы в OpenAPI](additional-responses.md){.internal-link target=_blank}. + +## Дополнения OpenAPI + +Когда вы объявляете *операцию пути* в вашем приложении, **FastAPI** автоматически генерирует соответствующие метаданные об этой *операции пути*, чтобы включить их в схему OpenAPI. + +/// note | Технические детали + +В спецификации OpenAPI это называется Объект операции. + +/// + +Это содержит всю информацию о *операции пути* и используется для генерации автоматической документации. + +Это включает в себя `tags`, `parameters`, `requestBody`, `responses` и т.д. + +Эта схема OpenAPI для данной *операции пути* обычно генерируется автоматически **FastAPI**, но ее можно и расширить. + +/// tip | Совет + +Это точка низкоуровневого расширения. + +Если вам нужно только объявить дополнительные ответы, более удобный способ сделать это — через [Дополнительные ответы в OpenAPI](additional-responses.md){.internal-link target=_blank}. + +/// + +Вы можете расширить схему OpenAPI для *операции пути* с помощью параметра `openapi_extra`. + +### Расширения OpenAPI + +Этот `openapi_extra` может быть полезен, например, чтобы объявить [Расширения OpenAPI](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions): + +{* ../../docs_src/path_operation_advanced_configuration/tutorial005.py hl[6] *} + +Если вы откроете автоматическую документацию API, ваше расширение появится внизу конкретной *операции пути*. + + + +И если вы увидите результат OpenAPI (по адресу `/openapi.json` в вашем API), вы увидите ваше расширение также как часть конкретной *операции пути*: + +```JSON hl_lines="22" +{ + "openapi": "3.1.0", + "info": { + "title": "FastAPI", + "version": "0.1.0" + }, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + }, + "x-aperture-labs-portal": "blue" + } + } + } +} +``` + +### Пользовательская схема OpenAPI *операции пути* + +Словарь в `openapi_extra` будет глубоко объединен с автоматически сгенерированной схемой OpenAPI для *операции пути*. + +Так вы можете добавить дополнительные данные в автоматически сгенерированную схему. + +Например, вы можете решить сами считывать и валидировать запрос без использования автоматических функций FastAPI с Pydantic, но вы все равно можете захотеть определить запрос в схеме OpenAPI. + +Вы можете сделать это с помощью `openapi_extra`: + +{* ../../docs_src/path_operation_advanced_configuration/tutorial006.py hl[19:36, 39:40] *} + +В этом примере мы не объявили никакой Pydantic-модели. На самом деле тело запроса даже не проанализировано как JSON, оно считывается напрямую как `bytes`, и функция `magic_data_reader()` будет отвечать за его парсинг. + +Тем не менее, мы можем объявить ожидаемую схему для тела запроса. + +### Пользовательский тип содержимого OpenAPI + +Используя этот же трюк, вы можете использовать Pydantic-модель для определения JSON-схемы, которая затем будет включена в пользовательский раздел схемы OpenAPI для *операции пути*. + +И вы можете сделать это даже если тип данных в запросе не JSON. + +Например, в этом приложении мы не используем интегрированные функции FastAPI для извлечения JSON-схемы из Pydantic-моделей и автоматической валидации для JSON. В самом деле, мы объявляем тип содержимого запроса как YAML, а не JSON: + +//// tab | Pydantic v2 + +{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[17:22, 24] *} + +//// + +//// tab | Pydantic v1 + +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py hl[17:22, 24] *} + +//// + +/// info | Информация + +В версии Pydantic 1 метод для получения JSON-схемы для модели назывался `Item.schema()`, в версии Pydantic 2 метод называется `Item.model_json_schema()`. + +/// + +Тем не менее, хотя мы не используем стандартную интегрированную функциональность, мы все же используем модель Pydantic, чтобы вручную сгенерировать JSON-схему для данных, которые мы хотим получить в YAML. + +Затем мы используем запрос напрямую и извлекаем тело как `bytes`. Это означает, что FastAPI даже не будет пытаться парсить содержимое запроса как JSON. + +И затем в нашем коде мы парсим это YAML-содержимое напрямую, и затем снова используем ту же модель Pydantic для валидации содержимого YAML: + +//// tab | Pydantic v2 + +{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[26:33] *} + +//// + +//// tab | Pydantic v1 + +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py hl[26:33] *} + +//// + +/// info | Информация + +В версии Pydantic 1 метод для парсинга и валидации объекта назывался `Item.parse_obj()`, в версии Pydantic 2 метод называется `Item.model_validate()`. + +/// + +/// tip | Совет + +Здесь мы повторно используем ту же модель Pydantic. + +Но таким же образом можно было бы валидировать ее каким-то другим способом. + +/// diff --git a/docs/ru/docs/advanced/response-headers.md b/docs/ru/docs/advanced/response-headers.md new file mode 100644 index 000000000..d57b70c3e --- /dev/null +++ b/docs/ru/docs/advanced/response-headers.md @@ -0,0 +1,41 @@ +# HTTP-заголовки ответа + +## Использование параметра `Response` + +Вы можете объявить параметр типа `Response` в вашей *функции-обработчике пути* (так же, как вы делаете для cookie). + +Затем вы можете установить HTTP-заголовки в этом *временном* объекте ответа. + +{* ../../docs_src/response_headers/tutorial002.py hl[1, 7:8] *} + +После этого вы можете вернуть любой объект, который вам нужен, как обычно (например, `dict`, модель базы данных и т.д.). + +Если вы объявили `response_model`, он все равно будет использован для фильтрации и преобразования объекта, который вы вернули. + +**FastAPI** использует этот *временный* ответ для извлечения HTTP-заголовков (также cookie и статус-код ответа) и помещает их в окончательный ответ, который содержит значение, которое вы вернули и которое фильтруется любым `response_model`. + +Вы также можете объявить параметр `Response` в зависимостях и установить HTTP-заголовки (и cookie) в них. + +## Непосредственный возврат `Response` + +Вы также можете добавить HTTP-заголовки при непосредственном возврате объекта типа `Response`. + +Создайте ответ, как описано в разделе [Непосредственный возврат ответа](response-directly.md){.internal-link target=_blank}, и передайте HTTP-заголовки в качестве дополнительного параметра: + +{* ../../docs_src/response_headers/tutorial001.py hl[10:12] *} + +/// note | Технические детали + +Вы также могли бы использовать `from starlette.responses import Response` или `from starlette.responses import JSONResponse`. + +**FastAPI** предоставляет те же `starlette.responses`, что и `fastapi.responses`, для удобства разработчика. Но большинство доступных ответов поступает непосредственно из Starlette. + +И так как `Response` может часто использоваться для установки HTTP-заголовков и cookie, **FastAPI** также предоставляет его в `fastapi.Response`. + +/// + +## Пользовательские заголовки + +Имейте в виду, что можно добавлять проприетарные пользовательские HTTP-заголовки с использованием префикса 'X-'. + +Но если у вас есть пользовательские заголовки, которые вы хотите, чтобы клиент в браузере мог видеть, вам нужно добавить их в ваши CORS-настройки (прочтите больше в разделе [CORS (Cross-Origin Resource Sharing)](../tutorial/cors.md){.internal-link target=_blank}), используя параметр `expose_headers`, документированный в документации Starlette по CORS. diff --git a/docs/ru/docs/advanced/security/http-basic-auth.md b/docs/ru/docs/advanced/security/http-basic-auth.md new file mode 100644 index 000000000..1a2e1d467 --- /dev/null +++ b/docs/ru/docs/advanced/security/http-basic-auth.md @@ -0,0 +1,107 @@ +# HTTP Basic Auth + +Для самых простых случаев можно использовать HTTP Basic Auth. + +В HTTP Basic Auth приложение ожидает HTTP-заголовок, содержащий имя пользователя и пароль. + +Если он не получит их, то вернет ошибку HTTP 401 "Unauthorized" (неавторизовано). + +Также будет возвращен HTTP-заголовок `WWW-Authenticate` со значением `Basic` и опциональным параметром `realm`. + +Это говорит браузеру показать встроенную панель для ввода имени пользователя и пароля. + +Затем, когда вы вводите эти имя пользователя и пароль, браузер отправляет их в заголовке автоматически. + +## Простая HTTP Basic Auth + +* Импортируйте `HTTPBasic` и `HTTPBasicCredentials`. +* Создайте "`security` scheme" используя `HTTPBasic`. +* Используйте эту `security` зависимость в вашей *операции пути*. +* Она возвращает объект типа `HTTPBasicCredentials`: + * Содержит отправленные `username` и `password`. + +{* ../../docs_src/security/tutorial006_an_py39.py hl[4,8,12] *} + +Когда вы попытаетесь открыть URL в первый раз (или нажмете кнопку "Execute" в документации) браузер спросит вас имя пользователя и пароль: + + + +## Проверка имени пользователя + +Вот более полный пример. + +Используйте зависимость, чтобы проверить, правильные ли имя пользователя и пароль. + +Для этого используйте стандартный модуль Python `secrets` для проверки имени пользователя и пароля. + +`secrets.compare_digest()` требует `bytes` или `str`, содержащий только ASCII-символы (те, что на английском), это значит, что он не будет работать с символами, такими как `á`, например в `Sebastián`. + +Чтобы с этим справиться, мы сначала конвертируем `username` и `password` в `bytes`, кодируя их в UTF-8. + +Затем мы можем использовать `secrets.compare_digest()` для гарантии, что `credentials.username` равен `"stanleyjobson"`, а `credentials.password` равен `"swordfish"`. + +{* ../../docs_src/security/tutorial007_an_py39.py hl[1,12:24] *} + +Это будет аналогично: + +```Python +if not (credentials.username == "stanleyjobson") or not (credentials.password == "swordfish"): + # Return some error + ... +``` + +Но, используя `secrets.compare_digest()`, это будет защищено от атак под названием "тайминговые атаки". + +### Тайминговые атаки + +Что такое "тайминговая атака"? + +Представим, что некоторая группа злоумышленников пытается угадать имя пользователя и пароль. + +Они отправляют запрос с именем пользователя `johndoe` и паролем `love123`. + +Тогда Python-код в вашем приложении будет эквивалентен чему-то вроде: + +```Python +if "johndoe" == "stanleyjobson" and "love123" == "swordfish": + ... +``` + +Но в тот момент, когда Python сравнивает первую `j` в `johndoe` с первой `s` в `stanleyjobson`, он вернет `False`, так как уже знает, что эти две строки не одинаковы, считая, что "нет смысла тратить больше вычислительных ресурсов на сравнение остальных букв". И ваше приложение скажет "Неправильное имя пользователя или пароль". + +Но затем злоумышленники попробуют с именем пользователя `stanleyjobsox` и паролем `love123`. + +И ваш код приложения делает что-то вроде: + +```Python +if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish": + ... +``` + +Python должен будет сравнить весь `stanleyjobso` в `stanleyjobsox` и `stanleyjobson`, чтобы понять, что строки не одинаковы. Так что это займет дополнительные микросекунды, чтобы ответить "Неправильное имя пользователя или пароль". + +#### Время ответа помогает злоумышленникам + +В этот момент, заметив, что серверу понадобилось немного больше времени, чтобы отправить ответ "Неправильное имя пользователя или пароль", злоумышленники узнают, что они что-то угадали, некоторые начальные буквы были правильными. + +И тогда они могут попробовать снова, зная, что это, вероятно, что-то более похожее на `stanleyjobsox`, чем на `johndoe`. + +#### "Профессиональная" атака + +Конечно, злоумышленники не будут делать все это вручную, они напишут программу, чтобы делать это, возможно, с тысячами или миллионами тестов в секунду. И они будут получать лишь одну правильную букву за раз. + +Но, делая так, за несколько минут или часов злоумышленники могли бы угадать правильное имя пользователя и пароль, с "помощью" нашего приложения, просто используя время, затраченное на ответ. + +#### Исправьте это с помощью `secrets.compare_digest()` + +Но в нашем коде мы фактически используем `secrets.compare_digest()`. + +Вкратце, это займет одинаковое время, чтобы сравнить `stanleyjobsox` с `stanleyjobson`, как и `johndoe` с `stanleyjobson`. И то же самое для пароля. + +Таким образом, используя `secrets.compare_digest()` в коде вашего приложения, оно будет защищено от этого целого спектра атак на безопасность. + +### Возврат ошибки + +После обнаружения, что учетные данные неверны, верните `HTTPException` с кодом состояния 401 (тот же, что возвращается, когда учетные данные не предоставлены) и добавьте заголовок `WWW-Authenticate`, чтобы браузер снова отобразил приглашение для входа: + +{* ../../docs_src/security/tutorial007_an_py39.py hl[26:30] *} diff --git a/docs/ru/docs/advanced/security/index.md b/docs/ru/docs/advanced/security/index.md new file mode 100644 index 000000000..9d681c7fd --- /dev/null +++ b/docs/ru/docs/advanced/security/index.md @@ -0,0 +1,19 @@ +# Расширенная безопасность + +## Дополнительные функции + +Существуют дополнительные функции для обеспечения безопасности, кроме тех, которые рассмотрены в [Руководстве пользователя: Безопасность](../../tutorial/security/index.md){.internal-link target=_blank}. + +/// tip | Совет + +Следующие разделы **не обязательно "продвинутые"**. + +И возможно, что для вашего случая решения находятся в одном из них. + +/// + +## Сначала прочтите Руководство + +Следующие разделы предполагают, что вы уже прочитали основное [Руководство пользователя: Безопасность](../../tutorial/security/index.md){.internal-link target=_blank}. + +Все они основаны на тех же концепциях, но позволяют использовать некоторые дополнительные функции. diff --git a/docs/ru/docs/advanced/security/oauth2-scopes.md b/docs/ru/docs/advanced/security/oauth2-scopes.md new file mode 100644 index 000000000..9a2b7f203 --- /dev/null +++ b/docs/ru/docs/advanced/security/oauth2-scopes.md @@ -0,0 +1,274 @@ +# OAuth2 scope + +Вы можете использовать OAuth2 scope напрямую с **FastAPI**, они интегрированы для работы без проблем. + +Это позволит вам иметь более тонкую систему разрешений, следуя стандарту OAuth2, интегрированную в ваше OpenAPI приложение (и документы API). + +OAuth2 с scope — это механизм, используемый многими крупными провайдерами аутентификации, такими как Facebook, Google, GitHub, Microsoft, Twitter и т.д. Они используют его для предоставления определенных разрешений пользователям и приложениям. + +Каждый раз, когда вы "входите с помощью" Facebook, Google, GitHub, Microsoft, Twitter, это приложение использует OAuth2 с scope. + +В этом разделе вы увидите, как управлять аутентификацией и авторизацией с использованием того же OAuth2 с scope в вашем приложении **FastAPI**. + +/// warning | Предупреждение + +Это более или менее сложный раздел. Если вы только начинаете, вы можете пропустить его. + +Вы не обязательно нуждаетесь в OAuth2 scope, и можете обрабатывать аутентификацию и авторизацию, как вам угодно. + +Но OAuth2 с scope может быть прекрасно интегрирован в ваш API (с OpenAPI) и в ваши документы API. + +Тем не менее, вы все равно должны применять эти scope, или любые другие требования к безопасности/авторизации, так как вам нужно, в вашем коде. + +В многих случаях, OAuth2 с scope может быть избыточными. + +Но если вы знаете, что они вам нужны, или вы заинтересовались, продолжайте чтение. + +/// + +## OAuth2 scope и OpenAPI + +Спецификация OAuth2 определяет "scope" как список строк, разделенных пробелами. + +Содержимое каждой из этих строк может иметь любой формат, но не должно содержать пробелов. + +Эти scope представляют собой "разрешения". + +В OpenAPI (например, в документах API) вы можете определить "схемы безопасности". + +Когда одна из этих схем безопасности использует OAuth2, вы также можете объявлять и использовать scope. + +Каждый "scope" — это просто строка (без пробелов). + +Обычно они используются для объявления определенных разрешений безопасности, например: + +* `users:read` или `users:write` — это распространенные примеры. +* `instagram_basic` используется Facebook/Instagram. +* `https://www.googleapis.com/auth/drive` используется Google. + +/// info | Информация + +В OAuth2 "scope" — это просто строка, указывающая на конкретные требуемые разрешения. + +Не имеет значения, есть ли в нем другие символы, такие как `:`, или если это URL. + +Эти детали специфичны для реализации. + +Для OAuth2 они просто строки. + +/// + +## Общее представление + +Сначала давайте быстро посмотрим на части, которые изменяются по сравнению с примерами в основном **Учебнике - Руководстве пользователя** для [OAuth2 с Паролем (и хешированием), Bearer с JWT токенами](../../tutorial/security/oauth2-jwt.md){.internal-link target=_blank}. Теперь используя OAuth2 scope: + +{* ../../docs_src/security/tutorial005_an_py310.py hl[5,9,13,47,65,106,108:116,122:125,129:135,140,156] *} + +Теперь давайте рассмотрим эти изменения шаг за шагом. + +## Схема безопасности OAuth2 + +Первое изменение состоит в том, что мы теперь объявляем схему безопасности OAuth2 с двумя доступными scope, `me` и `items`. + +Параметр `scopes` принимает `dict`, где каждый scope указывается в качестве ключа, а описание — в качестве значения: + +{* ../../docs_src/security/tutorial005_an_py310.py hl[63:66] *} + +Поскольку мы теперь объявляем эти scope, они будут отображаться в документации API, когда вы входите/авторизируетесь. + +И вы сможете выбрать, к каким scope вы хотите дать доступ: `me` и `items`. + +Это тот же механизм, который используется, когда вы предоставляете разрешения при входе в Facebook, Google, GitHub и т.д: + + + +## JWT токен с scope + +Теперь модифицируйте токен *операции пути*, чтобы вернуть запрашиваемые scope. + +Мы по-прежнему используем тот же `OAuth2PasswordRequestForm`. Он включает свойство `scopes` с `list` из `str`, в котором содержатся все scope, полученные в запросе. + +И мы возвращаем scope как часть JWT токена. + +/// danger | Опасность + +Для упрощения, здесь мы просто добавляем полученные scope непосредственно в токен. + +Но в вашем приложении, с целью безопасности, вы должны удостовериться, что добавляете только те scope, которые пользователь действительно может иметь, или те, которые вы заранее определили. + +/// + +{* ../../docs_src/security/tutorial005_an_py310.py hl[156] *} + +## Объявление scope в *операциях пути* и зависимостях + +Теперь мы объявляем, что *операция пути* для `/users/me/items/` требует scope `items`. + +Для этого мы импортируем и используем `Security` из `fastapi`. + +Вы можете использовать `Security` для объявления зависимостей (так же как `Depends`), но `Security` также принимает параметр `scopes` со списком scope (строк). + +В данном случае, мы передаем функцию зависимости `get_current_active_user` в `Security` (так же как мы бы делали это с `Depends`). + +Но мы также передаем `list` с scope, в этом случае только с одним scope: `items` (хотя их может быть больше). + +И функция зависимости `get_current_active_user` также может объявлять подзависимости, не только с `Depends`, но и с `Security`. Объявляя свою подзависимость (`get_current_user`), а также требования к scope. + +В этом случае требуется scope `me` (могут требоваться и другие scope). + +/// note | Примечание + +Вам не обязательно нужно добавлять разные scope в разные места. + +Мы делаем это здесь, чтобы продемонстрировать, как **FastAPI** обрабатывает scope, объявленные на разных уровнях. + +/// + +{* ../../docs_src/security/tutorial005_an_py310.py hl[5,140,171] *} + +/// info | Технические детали + +`Security` на самом деле является подклассом `Depends`, и у него есть всего один дополнительный параметр, который мы рассмотрим позже. + +Но используя `Security` вместо `Depends`, **FastAPI** будет знать, что он может объявить scope безопасности, использовать их внутри и документировать API с помощью OpenAPI. + +Но когда вы импортируете `Query`, `Path`, `Depends`, `Security` и другие из `fastapi`, это на самом деле функции, которые возвращают специальные классы. + +/// + +## Используйте `SecurityScopes` + +Теперь обновите зависимость `get_current_user`. + +Это та зависимость, которая используется выше. + +Вот где мы используем ту же схему OAuth2, которую создали ранее, объявляя ее как зависимость: `oauth2_scheme`. + +Поскольку эта функция зависимости не имеет собственных требований к scope, мы можем использовать `Depends` с `oauth2_scheme`, нам не нужно использовать `Security`, когда нам не нужно указывать scope безопасности. + +Также мы объявляем специальный параметр типа `SecurityScopes`, импортированный из `fastapi.security`. + +Этот класс `SecurityScopes` похож на `Request` (`Request` использовался для получения объекта запроса напрямую). + +{* ../../docs_src/security/tutorial005_an_py310.py hl[9,106] *} + +## Используйте `scopes` + +Параметр `security_scopes` будет типа `SecurityScopes`. + +У него есть свойство `scopes` со списком, содержащим все scope, требуемые как сама зависимость, так и все зависимости, которые используют это как подзависимость. Это значит, все "зависимые"... это может звучать запутанно, но это объясняется снова позже. + +Объект `security_scopes` (класса `SecurityScopes`) также предоставляет атрибут `scope_str` с одной строкой, содержащей эти scope, разделенные пробелами (мы собираемся использовать его). + +Мы создаем `HTTPException`, который мы можем использовать (`raise`) позже в нескольких повторяющихся моментах. + +В этом исключении мы включаем требуемые scope (если они есть) в виде строки, разделенной пробелами (используя `scope_str`). Мы помещаем эту строку, содержащую scope, в HTTP-заголовке `WWW-Authenticate` (это часть спецификации). + +{* ../../docs_src/security/tutorial005_an_py310.py hl[106,108:116] *} + +## Проверьте `username` и форму данных + +Мы проверяем, что получили `username`, и извлекаем scope. + +Затем мы валидируем эти данные с помощью Pydantic-модели (обрабатывая исключение `ValidationError`), и если мы получаем ошибку при чтении JWT токена или валидации данных с Pydantic, мы вызываем `HTTPException`, который создали ранее. + +Для этого мы обновляем Pydantic-модель `TokenData` с новым свойством `scopes`. + +Валидируя данные с помощью Pydantic, мы можем убедиться, что у нас, например, точно есть `list` из `str` с scope и `str` с `username`. + +Вместо, например, `dict`, или чего-то еще, так как это может сломать приложение в какой-то момент позже, создавая угрозу безопасности. + +Мы также проверяем, что у нас есть пользователь с этим именем пользователя, и если нет, вызываем то же самое исключение, созданное ранее. + +{* ../../docs_src/security/tutorial005_an_py310.py hl[47,117:128] *} + +## Проверьте `scopes` + +Теперь мы проверяем, все ли требуемые scope, этой зависимостью и всеми зависимыми (включая *операции пути*), включены в предоставленные токеном scope, в противном случае вызываем `HTTPException`. + +Для этого мы используем `security_scopes.scopes`, который содержит `list` со всеми этими scope как `str`. + +{* ../../docs_src/security/tutorial005_an_py310.py hl[129:135] *} + +## Дерево зависимостей и scope + +Давайте еще раз рассмотрим это дерево зависимостей и scope. + +Поскольку зависимость `get_current_active_user` имеет подзависимость на `get_current_user`, scope `"me"`, объявленный в `get_current_active_user`, будет включен в список требуемых scope в `security_scopes.scopes`, переданном в `get_current_user`. + +Сама *операция пути* также объявляет scope, `"items"`, так что он также будет в списке `security_scopes.scopes`, передаваемом в `get_current_user`. + +Вот как выглядит иерархия зависимостей и scope: + +* У *операции пути* `read_own_items`: + * Требуемые scope `["items"]` с зависимостью: + * `get_current_active_user`: + * Функция зависимости `get_current_active_user` имеет: + * Требуемые scope `["me"]` с зависимостью: + * `get_current_user`: + * У функции зависимости `get_current_user`: + * Нет требуемых scope. + * Зависимость, использующая `oauth2_scheme`. + * Параметр `security_scopes` типа `SecurityScopes`: + * У этого параметра `security_scopes` есть свойство `scopes` со `list`, содержащим все scope, объявленные выше, так что: + * `security_scopes.scopes` будет содержать `["me", "items"]` для *операции пути* `read_own_items`. + * `security_scopes.scopes` будет содержать `["me"]` для *операции пути* `read_users_me`, потому что он объявлен в зависимости `get_current_active_user`. + * `security_scopes.scopes` будет содержать `[]` (ничего) для *операции пути* `read_system_status`, потому что там не объявлено никакое `Security` с `scopes`, и его зависимость `get_current_user` также не объявляет никакие `scopes`. + +/// tip | Совет + +Самое важное и "магическое" здесь то, что `get_current_user` будет иметь разный список `scopes` для проверки для каждой *операции пути*. + +Все зависит от `scopes`, объявленных в каждой *операции пути* и каждой зависимости в дереве зависимостей для данной конкретной *операции пути*. + +/// + +## Подробности о `SecurityScopes` + +Вы можете использовать `SecurityScopes` в любой точке и в нескольких местах, он не должен быть на уровне "корневой" зависимости. + +Он будет всегда иметь scope безопасности, объявленные в текущих зависимостях `Security` и всех зависимых для **конкретно этой** *операции пути* и **конкретно этого** дерева зависимостей. + +Поскольку `SecurityScopes` будут иметь все scope, заявленные зависимыми, вы можете использовать его для проверки того, что токен имеет требуемые scope в центральной функции зависимости, а затем объявлять разные требования к scope в разных *операциях пути*. + +Они будут проверяться независимо для каждой *операции пути*. + +## Проверьте это + +Если вы откроете документацию API, вы можете аутентифицироваться и указать, какие scope вы хотите авторизовать. + + + +Если вы не выберете ни один scope, вы будете "аутентифицированы", но при попытке доступа к `/users/me/` или `/users/me/items/` вы получите ошибку, сообщающую, что у вас недостаточно разрешений. Вы все равно сможете получить доступ к `/status/`. + +И если вы выберете scope `me`, но не scope `items`, вы сможете получить доступ к `/users/me/`, но не к `/users/me/items/`. + +Это именно то, что произойдет с сторонним приложением, которое пытается получить доступ к одной из этих *операций пути* с токеном, предоставленным пользователем, в зависимости от того, сколько разрешений пользователь дал приложению. + +## О сторонних интеграциях + +В этом примере мы используем OAuth2 "парольный" поток. + +Это уместно, когда мы входим в наше собственное приложение, вероятно, с нашим собственным фронтендом. + +Потому что мы можем доверять ему получение `username` и `password`, так как мы его контролируем. + +Но если вы строите приложение OAuth2, к которому будут подключаться другие (т.е. если вы создаете провайдер аутентификации, эквивалентный Facebook, Google, GitHub и т.д.), вы должны использовать один из других потоков. + +Наиболее распространенным является поток implicit. + +Наиболее безопасным является поток с кодом, но его сложнее реализовать, так как он требует больше шагов. Поскольку он более сложен, многие провайдеры в конечном итоге предлагают поток implicit. + +/// note | Примечание + +Часто каждый провайдер аутентификации называет свои потоки по-разному, чтобы сделать это частью своего бренда. + +Но в конечном итоге они реализуют тот же стандарт OAuth2. + +/// + +**FastAPI** включает утилиты для всех этих потоков аутентификации OAuth2 в `fastapi.security.oauth2`. + +## `Security` в `dependencies` декоратора + +Таким же образом, как вы можете определять `list` из `Depends` в параметре `dependencies` декоратора (как объяснено в [Dependencies в декораторах операций пути](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), вы также можете использовать `Security` с `scopes` там. diff --git a/docs/ru/docs/advanced/settings.md b/docs/ru/docs/advanced/settings.md new file mode 100644 index 000000000..7bbeff158 --- /dev/null +++ b/docs/ru/docs/advanced/settings.md @@ -0,0 +1,346 @@ +# Настройки и переменные окружения + +Во многих случаях вашим приложениям могут понадобиться внешние настройки или конфигурации, например, секретные ключи, учетные данные базы данных, учетные данные для почтовых сервисов и т.д. + +Большинство этих настроек являются изменяемыми (могут изменяться), как, например, URL-ы баз данных. Многие из них могут быть чувствительными, как, например, секреты. + +По этой причине их обычно предоставляют в виде переменных окружения, которые считываются приложением. + +/// tip | Совет + +Чтобы понять, что такое переменные окружения, можете прочитать [Переменные окружения](../environment-variables.md){.internal-link target=_blank}. + +/// + +## Типы и валидация + +Эти переменные окружения могут обрабатывать только текстовые строки, так как они являются внешними для Python и должны быть совместимы с другими программами и остальной системой (и даже с разными операционными системами, такими как Linux, Windows, macOS). + +Это означает, что любое значение, считанное в Python из переменной окружения, будет типа `str`, и любое преобразование в другой тип или валидация должны осуществляться в коде. + +## Pydantic `Settings` + +К счастью, Pydantic предоставляет отличную утилиту для работы с этими настройками, поступающими из переменных окружения, используя Pydantic: Управление настройками. + +### Установка `pydantic-settings` + +Сначала убедитесь, что вы создали ваше [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активировали его и затем установили пакет `pydantic-settings`: + +
+ +```console +$ pip install pydantic-settings +---> 100% +``` + +
+ +Этот пакет также включен, если вы устанавливаете все зависимости с: + +
+ +```console +$ pip install "fastapi[all]" +---> 100% +``` + +
+ +/// info | Информация + +В Pydantic v1 он поставлялся с основным пакетом. Теперь он распространяется как отдельный пакет, чтобы вы могли выбрать, устанавливать его или нет, если вам не нужна эта функциональность. + +/// + +### Создание объекта `Settings` + +Импортируйте `BaseSettings` из Pydantic и создайте подкласс, как это делается с Pydantic-моделью. + +Так же, как и с Pydantic-моделями, вы объявляете атрибуты класса с аннотациями типов и, возможно, значениями по умолчанию. + +Вы можете использовать все те же функции валидации и инструменты, которые используете в Pydantic-моделях, например, разные типы данных и дополнительные валидации с `Field()`. + +//// tab | Pydantic v2 + +{* ../../docs_src/settings/tutorial001.py hl[2,5:8,11] *} + +//// + +//// tab | Pydantic v1 + +/// info | Информация + +В Pydantic v1 вы импортировали бы `BaseSettings` непосредственно из `pydantic` вместо `pydantic_settings`. + +/// + +{* ../../docs_src/settings/tutorial001_pv1.py hl[2,5:8,11] *} + +//// + +/// tip | Совет + +Если вам нужно что-то быстрое для копирования и вставки, не используйте этот пример, используйте последний ниже. + +/// + +Когда вы создаете экземпляр этого класса `Settings` (в данном случае, в объекте `settings`), Pydantic будет считывать переменные окружения без учета регистра, так что переменная в верхнем регистре `APP_NAME` все еще будет считана для атрибута `app_name`. + +Затем он выполнит преобразование и валидацию данных. Таким образом, когда вы используете этот объект `settings`, у вас будут данные тех типов, которые вы объявили (например, `items_per_user` будет типа `int`). + +### Использование `settings` + +Затем вы можете использовать новый объект `settings` в вашем приложении: + +{* ../../docs_src/settings/tutorial001.py hl[18:20] *} + +### Запуск сервера + +Далее вы можете запустить сервер, передавая конфигурации как переменные окружения, например, вы можете установить `ADMIN_EMAIL` и `APP_NAME` следующим образом: + +
+ +```console +$ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +/// tip | Совет + +Чтобы установить несколько переменных окружения для одной команды, просто разделите их пробелами и укажите перед командой. + +/// + +Тогда настройка `admin_email` будет установлена в `"deadpool@example.com"`. + +Настройка `app_name` будет иметь значение `"ChimichangApp"`. + +А `items_per_user` сохранит свое значение по умолчанию — `50`. + +## Настройки в другом модуле + +Вы можете поместить эти настройки в другой файл-модуль, как вы видели в [Более крупные приложения - Несколько файлов](../tutorial/bigger-applications.md){.internal-link target=_blank}. + +Например, у вас может быть файл `config.py` с: + +{* ../../docs_src/settings/app01/config.py *} + +И затем использовать его в файле `main.py`: + +{* ../../docs_src/settings/app01/main.py hl[3,11:13] *} + +/// tip | Совет + +Вам также нужен будет файл `__init__.py`, как вы видели в [Более крупные приложения - Несколько файлов](../tutorial/bigger-applications.md){.internal-link target=_blank}. + +/// + +## Настройки в зависимости + +В некоторых случаях может быть полезно предоставлять настройки из зависимости, вместо использования глобального объекта `settings`, который используется везде. + +Это может быть особенно полезно во время тестирования, так как очень легко переопределить зависимость вашими собственными настройками. + +### Файл конфигурации + +Исходя из предыдущего примера, ваш файл `config.py` может выглядеть так: + +{* ../../docs_src/settings/app02/config.py hl[10] *} + +Обратите внимание, что теперь мы не создаем экземпляр по умолчанию `settings = Settings()`. + +### Основной файл приложения + +Теперь мы создаем зависимость, которая возвращает новый `config.Settings()`. + +{* ../../docs_src/settings/app02_an_py39/main.py hl[6,12:13] *} + +/// tip | Совет + +Мы обсудим `@lru_cache` чуть позже. + +На данный момент можно считать, что `get_settings()` — это обычная функция. + +/// + +А затем мы можем потребовать его в *функции-обработчике пути* как зависимость и использовать ее в любом месте, где это необходимо. + +{* ../../docs_src/settings/app02_an_py39/main.py hl[17,19:21] *} + +### Настройки и тестирование + +Затем вам будет очень легко предоставить другой объект настроек во время тестирования, создав переопределение зависимости для `get_settings`: + +{* ../../docs_src/settings/app02/test_main.py hl[9:10,13,21] *} + +В переопределении зависимости мы устанавливаем новое значение для `admin_email`, создавая новый объект `Settings`, а затем возвращаем этот новый объект. + +Затем мы можем протестировать, что он используется. + +## Чтение файла `.env` + +Если у вас есть множество настроек, которые могут часто меняться, возможно, в разных окружениях, может быть полезно поместить их в файл и затем считывать их из него, как если бы они были переменными окружения. + +Эта практика достаточно распространена, чтобы у нее было название, эти переменные окружения обычно помещаются в файл `.env`, и этот файл называется "dotenv". + +/// tip | Совет + +Файл, начинающийся с точки (`.`), является скрытым файлом в системах, похожих на Unix, таких как Linux и macOS. + +Но файл dotenv не обязательно должен иметь именно такое имя. + +/// + +Pydantic поддерживает считывание из таких файлов с использованием внешней библиотеки. Вы можете узнать больше на Pydantic Settings: Поддержка Dotenv (.env). + +/// tip | Совет + +Для этого необходимо выполнить `pip install python-dotenv`. + +/// + +### Файл `.env` + +Вы могли бы иметь файл `.env` с: + +```bash +ADMIN_EMAIL="deadpool@example.com" +APP_NAME="ChimichangApp" +``` + +### Чтение настроек из `.env` + +А затем обновить ваш `config.py` следующим образом: + +//// tab | Pydantic v2 + +{* ../../docs_src/settings/app03_an/config.py hl[9] *} + +/// tip | Совет + +Атрибут `model_config` используется только для настройки Pydantic. Вы можете прочитать больше на Pydantic: Концепции: Конфигурация. + +/// + +//// + +//// tab | Pydantic v1 + +{* ../../docs_src/settings/app03_an/config_pv1.py hl[9:10] *} + +/// tip | Совет + +Класс `Config` используется только для настройки Pydantic. Вы можете прочитать больше на Конфигурация Pydantic модели. + +/// + +//// + +/// info | Информация + +В версии Pydantic 1 конфигурация осуществлялась во внутреннем классе `Config`, в версии Pydantic 2 это делается в атрибуте `model_config`. Этот атрибут принимает `dict`, и чтобы получить автозавершение и встроенные ошибки, вы можете импортировать и использовать `SettingsConfigDict` для определения этого `dict`. + +/// + +Здесь мы определяем конфигурацию `env_file` внутри вашего класса Pydantic `Settings` и устанавливаем значение для имени файла с файлом dotenv, который мы хотим использовать. + +### Создание `Settings` только один раз с `lru_cache` + +Чтение файла с диска обычно является дорогой (медленной) операцией, поэтому, вероятно, вы захотите делать это только один раз, а затем использовать тот же объект настроек, вместо того чтобы считывать его для каждого запроса. + +Но каждый раз, когда мы выполняем: + +```Python +Settings() +``` + +новый объект `Settings` будет создан, и при создании он снова считает файл `.env`. + +Если бы функция зависимости выглядела так: + +```Python +def get_settings(): + return Settings() +``` + +мы бы создавали этот объект для каждого запроса, и мы бы считывали файл `.env` для каждого запроса. ⚠️ + +Но так как мы используем декоратор `@lru_cache` сверху, объект `Settings` будет создан только один раз, при первом вызове. ✔️ + +{* ../../docs_src/settings/app03_an_py39/main.py hl[1,11] *} + +Затем для всех последующих вызовов `get_settings()` в зависимостях для следующих запросов, вместо выполнения внутреннего кода функции `get_settings()` и создания нового объекта `Settings`, будет возвращен тот же объект, который был возвращен при первом вызове, снова и снова. + +#### Технические детали `lru_cache` + +`@lru_cache` модифицирует функцию, которую он декорирует, чтобы возвращать то же самое значение, которое было возвращено в первый раз, вместо того чтобы вычислять его снова, выполняя код функции каждый раз. + +Таким образом, функция ниже будет выполнена один раз для каждой комбинации аргументов. А затем возвращаемые значения для каждой из этих комбинаций аргументов будут использоваться снова и снова, когда функция вызывается с точно такой же комбинацией аргументов. + +Например, если у вас есть функция: + +```Python +@lru_cache +def say_hi(name: str, salutation: str = "Ms."): + return f"Hello {salutation} {name}" +``` + +ваша программа может выполняться так: + +```mermaid +sequenceDiagram + +participant code as Код +participant function as say_hi() +participant execute as Выполнить код функции + + rect rgba(0, 255, 0, .1) + code ->> function: say_hi(name="Camila") + function ->> execute: выполнить код функции + execute ->> code: вернуть результат + end + + rect rgba(0, 255, 255, .1) + code ->> function: say_hi(name="Camila") + function ->> code: вернуть сохраненный результат + end + + rect rgba(0, 255, 0, .1) + code ->> function: say_hi(name="Rick") + function ->> execute: выполнить код функции + execute ->> code: вернуть результат + end + + rect rgba(0, 255, 0, .1) + code ->> function: say_hi(name="Rick", salutation="Mr.") + function ->> execute: выполнить код функции + execute ->> code: вернуть результат + end + + rect rgba(0, 255, 255, .1) + code ->> function: say_hi(name="Rick") + function ->> code: вернуть сохраненный результат + end + + rect rgba(0, 255, 255, .1) + code ->> function: say_hi(name="Camila") + function ->> code: вернуть сохраненный результат + end +``` + +В случае нашей зависимости `get_settings()`, функция даже не принимает никаких аргументов, поэтому всегда возвращает одно и то же значение. + +Таким образом, она ведет себя почти как глобальная переменная. Но поскольку используется функция зависимости, мы можем легко переопределить ее для тестирования. + +`@lru_cache` является частью `functools`, который является частью стандартной библиотеки Python, вы можете прочитать больше об этом в документации Python по `@lru_cache`. + +## Резюме + +Вы можете использовать Pydantic Settings для управления настройками или конфигурациями вашего приложения, используя все возможности Pydantic-моделей. + +* Используя зависимость, вы можете упростить тестирование. +* Вы можете использовать `.env` файлы с ним. +* Использование `@lru_cache` позволяет избежать повторного чтения файла dotenv для каждого запроса, при этом позволяя его переопределять во время тестирования. diff --git a/docs/ru/docs/advanced/sub-applications.md b/docs/ru/docs/advanced/sub-applications.md new file mode 100644 index 000000000..6126ce8b4 --- /dev/null +++ b/docs/ru/docs/advanced/sub-applications.md @@ -0,0 +1,67 @@ +# Подприложения - Mounts + +Если вам нужно иметь два независимых FastAPI приложения, каждое с собственным, независимым OpenAPI и собственными интерфейсами документации, вы можете создать основное приложение и "смонтировать" одно (или более) подприложение(я). + +## Монтирование **FastAPI** приложения + +"Монтирование" означает добавление полностью "независимого" приложения в определенный path, которое затем будет обрабатывать все под этим path, с _операциями пути_, объявленными в этом подприложении. + +### Высокоуровневое приложение + +Сначала создайте основное, высшего уровня, **FastAPI** приложение и его *операции пути*: + +{* ../../docs_src/sub_applications/tutorial001.py hl[3, 6:8] *} + +### Подприложение + +Затем создайте ваше подприложение и его *операции пути*. + +Это подприложение является стандартным FastAPI приложением, но именно оно будет "смонтировано": + +{* ../../docs_src/sub_applications/tutorial001.py hl[11, 14:16] *} + +### Монтирование подприложения + +В вашем высшего уровня приложении, `app`, смонтируйте подприложение, `subapi`. + +В этом случае оно будет смонтировано на path `/subapi`: + +{* ../../docs_src/sub_applications/tutorial001.py hl[11, 19] *} + +### Проверьте автоматическую документацию API + +Теперь запустите команду `fastapi` с вашим файлом: + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +И откройте документацию по адресу http://127.0.0.1:8000/docs. + +Вы увидите автоматическую документацию API для основного приложения, включающую лишь его собственные _операции пути_: + + + +Затем откройте документацию для подприложения по адресу http://127.0.0.1:8000/subapi/docs. + +Вы увидите автоматическую документацию API для подприложения, включающую только его собственные _операции пути_, все с правильным префиксом под-path `/subapi`: + + + +Если вы попробуете взаимодействовать с любым из двух пользовательских интерфейсов, они будут работать правильно, потому что браузер сможет обратиться к каждому конкретному приложению или подприложению. + +### Технические детали: `root_path` + +Когда вы монтируете подприложение, как описано выше, FastAPI позаботится о передаче пути монтирования для подприложения, используя механизм из спецификации ASGI, называемый `root_path`. + +Таким образом, подприложение будет знать, что необходимо использовать этот префикс пути для UI документации. + +И подприложение также может иметь свои собственные смонтированные подприложения, и всё будет работать корректно, потому что FastAPI обрабатывает все эти `root_path`s автоматически. + +Вы узнаете больше о `root_path` и том, как его использовать явно в разделе про [Работу за прокси-сервером](behind-a-proxy.md){.internal-link target=_blank}. diff --git a/docs/ru/docs/advanced/templates.md b/docs/ru/docs/advanced/templates.md new file mode 100644 index 000000000..06ed543b0 --- /dev/null +++ b/docs/ru/docs/advanced/templates.md @@ -0,0 +1,126 @@ +# Шаблоны + +Вы можете использовать любой шаблонизатор, который захотите, с **FastAPI**. + +Популярным выбором является Jinja2, который также используется Flask и другими инструментами. + +Существуют утилиты для его простой настройки, которые вы можете использовать непосредственно в своем приложении **FastAPI** (предоставлено Starlette). + +## Установка зависимостей + +Убедитесь, что вы создали [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активировали его и установили `jinja2`: + +
+ +```console +$ pip install jinja2 + +---> 100% +``` + +
+ +## Использование `Jinja2Templates` + +* Импортируйте `Jinja2Templates`. +* Создайте объект `templates`, который вы сможете повторно использовать позднее. +* Объявите параметр `Request` в *операции пути*, которая будет возвращать шаблон. +* Используйте созданные вами `templates` для рендеринга и возврата `TemplateResponse`, передайте имя шаблона, объект запроса и словарь "context" с парами "ключ-значение", которые будут использоваться внутри шаблона Jinja2. + +{* ../../docs_src/templates/tutorial001.py hl[4,11,15:18] *} + +/// note | Примечание + +До FastAPI 0.108.0, Starlette 0.29.0, параметр `name` был первым. + +Также, до этого, в предыдущих версиях объект `request` передавался как часть пар ключ-значение в контексте для Jinja2. + +/// + +/// tip | Совет + +Объявляя `response_class=HTMLResponse`, интерфейс документации сможет определить, что ответ будет в формате HTML. + +/// + +/// note | Технические детали + +Вы также можете использовать `from starlette.templating import Jinja2Templates`. + +**FastAPI** предоставляет тот же `starlette.templating` как `fastapi.templating` просто для вашего удобства, разработчик. Но большинство доступных ответов поступают непосредственно от Starlette. То же самое касается `Request` и `StaticFiles`. + +/// + +## Написание шаблонов + +Затем вы можете написать шаблон в `templates/item.html`, например: + +```jinja hl_lines="7" +{!../../docs_src/templates/templates/item.html!} +``` + +### Значения контекста шаблона + +В HTML, который содержит: + +{% raw %} + +```jinja +Item ID: {{ id }} +``` + +{% endraw %} + +...будет показан `id`, взятый из "context" `dict`, который вы передали: + +```Python +{"id": id} +``` + +Например, с `id`, равным `42`, это будет рендериться как: + +```html +Item ID: 42 +``` + +### Аргументы `url_for` в шаблоне + +Вы также можете использовать `url_for()` внутри шаблона; он принимает в качестве аргументов те же аргументы, которые использовались бы в вашей *функции-обработчике пути*. + +Таким образом, раздел с: + +{% raw %} + +```jinja + +``` + +{% endraw %} + +...сгенерирует ссылку на тот же URL, который был бы обработан *функцией-обработчиком пути* `read_item(id=id)`. + +Например, с `id`, равным `42`, это будет рендериться как: + +```html + +``` + +## Шаблоны и статические файлы + +Вы также можете использовать `url_for()` внутри шаблона и использовать его, например, с `StaticFiles`, которые вы смонтировали с `name="static"`. + +```jinja hl_lines="4" +{!../../docs_src/templates/templates/item.html!} +``` + +В этом примере это будет ссылка на CSS-файл по пути `static/styles.css` с: + +```CSS hl_lines="4" +{!../../docs_src/templates/static/styles.css!} +``` + +И поскольку вы используете `StaticFiles`, этот CSS-файл будет автоматически обслуживаться вашим приложением **FastAPI** по URL `/static/styles.css`. + +## Дополнительные сведения + +Для получения дополнительной информации, включая тестирование шаблонов, смотрите документацию Starlette по шаблонам. diff --git a/docs/ru/docs/advanced/testing-dependencies.md b/docs/ru/docs/advanced/testing-dependencies.md new file mode 100644 index 000000000..b48c89e8d --- /dev/null +++ b/docs/ru/docs/advanced/testing-dependencies.md @@ -0,0 +1,53 @@ +# Тестирование зависимостей с использованием переопределений + +## Переопределение зависимостей в процессе тестирования + +Существуют сценарии, когда вам может понадобиться переопределить зависимость во время тестирования. + +Вы не хотите, чтобы оригинальная зависимость выполнялась (также как и любые её подзависимости). + +Вместо этого, вы хотите предоставить другую зависимость, которая будет использоваться только во время тестов (возможно, только для некоторых конкретных тестов), и будет предоставлять значение, которое можно использовать там, где использовалось значение оригинальной зависимости. + +### Примеры использования: внешний сервис + +Примером может служить ситуация, когда у вас есть внешний провайдер аутентификации, которого вам необходимо вызывать. + +Вы отправляете ему токен, и он возвращает аутентифицированного пользователя. + +Этот провайдер может брать плату за каждый запрос, и вызов его может занять больше времени, чем если бы вы использовали фиксированного пользователя-мока для тестов. + +Вероятно, вы захотите протестировать внешнего провайдера один раз, но не обязательно вызывать его для каждого теста, который вы запускаете. + +В этом случае вы можете переопределить зависимость, которая вызывает этого провайдера, и использовать пользовательскую зависимость, которая возвращает мока-пользователя, только для ваших тестов. + +### Использование атрибута `app.dependency_overrides` + +В таких случаях ваше приложение **FastAPI** имеет атрибут `app.dependency_overrides`, который является простым `dict`. + +Чтобы переопределить зависимость для тестирования, вы указываете оригинальную зависимость (функцию) в качестве ключа, и ваше переопределение зависимости (другую функцию) в качестве значения. + +Затем **FastAPI** будет вызывать это переопределение вместо оригинальной зависимости. + +{* ../../docs_src/dependency_testing/tutorial001_an_py310.py hl[26:27,30] *} + +/// tip | Совет + +Вы можете установить переопределение зависимости для зависимости, используемой в любом месте вашего приложения **FastAPI**. + +Оригинальная зависимость может использоваться в *функции-обработчике пути*, *декораторе операции пути* (если вы не используете возвращаемое значение), вызове `.include_router()`, и т. д. + +FastAPI все равно сможет ее переопределить. + +/// + +Затем вы можете сбросить ваши переопределения (удалить их), установив `app.dependency_overrides` в пустой `dict`: + +```Python +app.dependency_overrides = {} +``` + +/// tip | Совет + +Если вы хотите переопределить зависимость только в некоторых тестах, вы можете установить переопределение в начале теста (внутри тестовой функции) и сбросить его в конце (в конце тестовой функции). + +/// diff --git a/docs/ru/docs/advanced/testing-events.md b/docs/ru/docs/advanced/testing-events.md new file mode 100644 index 000000000..12a33f7f6 --- /dev/null +++ b/docs/ru/docs/advanced/testing-events.md @@ -0,0 +1,5 @@ +# Тестирование событий: startup - shutdown + +Когда вам нужно, чтобы ваши обработчики событий (`startup` и `shutdown`) запускались в ваших тестах, вы можете использовать `TestClient` с оператором `with`: + +{* ../../docs_src/app_testing/tutorial003.py hl[9:12,20:24] *} diff --git a/docs/ru/docs/advanced/testing-websockets.md b/docs/ru/docs/advanced/testing-websockets.md new file mode 100644 index 000000000..8dd898a4f --- /dev/null +++ b/docs/ru/docs/advanced/testing-websockets.md @@ -0,0 +1,13 @@ +# Тестирование WebSockets + +Вы можете использовать тот же `TestClient` для тестирования WebSockets. + +Для этого вы используете `TestClient` в операторе `with`, подключаясь к WebSocket: + +{* ../../docs_src/app_testing/tutorial002.py hl[27:31] *} + +/// note | Заметка + +Для получения дополнительных сведений обратитесь к документации Starlette для тестирования WebSockets. + +/// diff --git a/docs/ru/docs/advanced/using-request-directly.md b/docs/ru/docs/advanced/using-request-directly.md new file mode 100644 index 000000000..ca81a4b0f --- /dev/null +++ b/docs/ru/docs/advanced/using-request-directly.md @@ -0,0 +1,56 @@ +# Прямое использование объекта Request + +До сих пор вы объявляли части HTTP-запроса, которые вам нужны, с указанием их типов. + +Получение данных из: + +* path как параметров. +* HTTP-заголовков. +* cookies. +* и т.д. + +И благодаря этому, **FastAPI** валидирует эти данные, выполняет их преобразование и автоматически генерирует документацию для вашего API. + +Но есть ситуации, когда может понадобиться доступ к объекту `Request` напрямую. + +## Подробности об объекте `Request` + +Так как **FastAPI** на самом деле построен на **Starlette**, с уровнем дополнительных инструментов сверху, вы можете использовать объект `Request` из Starlette напрямую, когда это необходимо. + +Это также означает, что если вы получаете данные из объекта `Request` напрямую (например, читаете тело запроса), они не будут валидироваться, преобразовываться или документироваться (с использованием OpenAPI для автоматического пользовательского интерфейса API) в FastAPI. + +Хотя любой другой параметр, объявленный обычно (например, тело запроса с Pydantic моделью), все равно будет валидироваться, преобразовываться, аннотироваться и т.д. + +Но есть определенные случаи, когда полезно получить объект `Request`. + +## Использование объекта `Request` напрямую + +Представьте, что вы хотите получить IP-адрес/хост клиента внутри вашей *функции-обработчика пути*. + +Для этого вам нужно будет получить доступ к запросу напрямую. + +{* ../../docs_src/using_request_directly/tutorial001.py hl[1,7:8] *} + +Объявляя параметр *функции-обработчика пути* с типом `Request`, **FastAPI** поймет, что необходимо передать объект `Request` в этот параметр. + +/// tip | Подсказка + +Обратите внимание, что в этом случае мы объявляем path параметр вместе с параметром запроса. + +Таким образом, path параметр будет извлечен, провалидирован, преобразован в указанный тип и аннотирован с помощью OpenAPI. + +Точно так же вы можете объявить любой другой параметр как обычно, и дополнительно получить также объект `Request`. + +/// + +## Документация по `Request` + +Вы можете прочитать больше деталей о объекте `Request` на официальном сайте документации Starlette. + +/// note | Технические Подробности + +Вы также можете использовать `from starlette.requests import Request`. + +**FastAPI** предоставляет это напрямую просто для удобства для вас, разработчика. Но эта функция приходит прямо из Starlette. + +/// diff --git a/docs/ru/docs/advanced/wsgi.md b/docs/ru/docs/advanced/wsgi.md new file mode 100644 index 000000000..519ae8e51 --- /dev/null +++ b/docs/ru/docs/advanced/wsgi.md @@ -0,0 +1,35 @@ +# Включение WSGI - Flask, Django и другие + +Вы можете монтировать WSGI-приложения, как вы видели в разделе [Дополнительные приложения - Монтирования](sub-applications.md){.internal-link target=_blank}, [За прокси](behind-a-proxy.md){.internal-link target=_blank}. + +Для этого вы можете использовать `WSGIMiddleware`, чтобы обернуть ваше WSGI-приложение, например, Flask, Django и т.д. + +## Использование `WSGIMiddleware` + +Вам нужно импортировать `WSGIMiddleware`. + +Затем обернуть WSGI (например, Flask) приложение с помощью middleware (промежуточного слоя). + +И затем смонтировать это под path. + +{* ../../docs_src/wsgi/tutorial001.py hl[2:3,3] *} + +## Проверьте это + +Теперь каждый HTTP-запрос по path `/v1/` будет обработан приложением Flask. + +А остальные запросы будут обработаны **FastAPI**. + +Если вы запустите его и перейдете по ссылке http://localhost:8000/v1/, вы увидите ответ от Flask: + +```txt +Hello, World from Flask! +``` + +И если вы перейдете по ссылке http://localhost:8000/v2, вы увидите ответ от FastAPI: + +```JSON +{ + "message": "Hello World" +} +``` diff --git a/docs/ru/docs/deployment/cloud.md b/docs/ru/docs/deployment/cloud.md new file mode 100644 index 000000000..fd523146f --- /dev/null +++ b/docs/ru/docs/deployment/cloud.md @@ -0,0 +1,17 @@ +# Развёртывание FastAPI на облачных провайдерах + +Вы можете использовать практически **любого облачного провайдера** для развертывания вашего приложения FastAPI. + +В большинстве случаев у основных облачных провайдеров есть руководства по развертыванию FastAPI с их помощью. + +## Облачные провайдеры - спонсоры + +Некоторые облачные провайдеры ✨ [**спонсируют FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, это обеспечивает продолжительное и здоровое **развитие** FastAPI и его **экосистемы**. + +Это демонстрирует их настоящую приверженность FastAPI и его **сообществу** (вам), так как они не только хотят предоставить вам **хороший сервис**, но и хотят убедиться, что у вас есть **хороший и здоровый фреймворк**, FastAPI. 🙇 + +Возможно, вам захочется попробовать их услуги и следовать их руководствам: + +* Platform.sh +* Porter +* Render diff --git a/docs/ru/docs/deployment/server-workers.md b/docs/ru/docs/deployment/server-workers.md new file mode 100644 index 000000000..1551b00d5 --- /dev/null +++ b/docs/ru/docs/deployment/server-workers.md @@ -0,0 +1,137 @@ +# Воркеры сервера - Uvicorn с воркерами + +Давайте снова рассмотрим концепции деплоя: + +* Безопасность - HTTPS +* Запуск при старте +* Перезапуски +* **Репликация (количество рабочих процессов)** +* Память +* Подготовительные шаги перед запуском + +До этого момента, с помощью всех туториалов в документации, вы, вероятно, запускали **серверную программу**, например, используя команду `fastapi`, которая запускает Uvicorn, выполняя **единственный процесс**. + +При деплое приложений вы, вероятно, захотите иметь некоторую **репликацию процессов**, чтобы воспользоваться преимуществами **многих ядер** и иметь возможность обрабатывать больше запросов. + +Как вы видели в предыдущей главе о [Концепции деплоя](concepts.md){.internal-link target=_blank}, существует множество стратегий, которые вы можете использовать. + +Здесь я покажу вам, как использовать **Uvicorn** с **воркер-процессами**, используя команду `fastapi` или непосредственно команду `uvicorn`. + +/// info | Информация + +Если вы используете контейнеры, например, с Docker или Kubernetes, я расскажу вам больше об этом в следующей главе: [FastAPI в контейнерах - Docker](docker.md){.internal-link target=_blank}. + +В частности, работая с **Kubernetes**, вы, вероятно, **не** захотите использовать воркеры, а вместо этого запускать **один процесс Uvicorn в контейнере**, но об этом я расскажу позже в той главе. + +/// + +## Несколько воркеров + +Вы можете запустить несколько воркеров, используя опцию командной строки `--workers`: + +//// tab | `fastapi` + +Если вы используете команду `fastapi`: + +
+ +```console +$ fastapi run --workers 4 main.py + + FastAPI Запуск продакшн-сервера 🚀 + + Поиск структуры файлов пакета в директориях с + __init__.py + Импорт из /home/user/code/awesomeapp + + module 🐍 main.py + + code Импортирование объекта приложения FastAPI из модуля со следующим кодом: + + from main import app + + app Использование строки импорта: main:app + + server Сервер стартовал на http://0.0.0.0:8000 + server Документация по адресу http://0.0.0.0:8000/docs + + Логи: + + INFO Uvicorn работает на http://0.0.0.0:8000 (Нажмите CTRL+C для выхода) + INFO Стартовал родительский процесс [27365] + INFO Стартовал серверный процесс [27368] + INFO Стартовал серверный процесс [27369] + INFO Стартовал серверный процесс [27370] + INFO Стартовал серверный процесс [27367] + INFO Ожидание запуска приложения. + INFO Ожидание запуска приложения. + INFO Ожидание запуска приложения. + INFO Ожидание запуска приложения. + INFO Запуск приложения завершён. + INFO Запуск приложения завершён. + INFO Запуск приложения завершён. + INFO Запуск приложения завершён. +``` + +
+ +//// + +//// tab | `uvicorn` + +Если вы предпочитаете использовать команду `uvicorn` напрямую: + +
+ +```console +$ uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4 +INFO: Uvicorn работает на http://0.0.0.0:8080 (Нажмите CTRL+C для выхода) +INFO: Стартовал родительский процесс [27365] +INFO: Стартовал серверный процесс [27368] +INFO: Ожидание запуска приложения. +INFO: Запуск приложения завершён. +INFO: Стартовал серверный процесс [27369] +INFO: Ожидание запуска приложения. +INFO: Запуск приложения завершён. +INFO: Стартовал серверный процесс [27370] +INFO: Ожидание запуска приложения. +INFO: Запуск приложения завершён. +INFO: Стартовал серверный процесс [27367] +INFO: Ожидание запуска приложения. +INFO: Запуск приложения завершён. +``` + +
+ +//// + +Единственная новая опция здесь - `--workers`, указывающая Uvicorn запустить 4 воркер-процесса. + +Вы также можете увидеть, что отображается **PID** каждого процесса: `27365` для родительского процесса (это **менеджер процессов**) и один для каждого воркер-процесса: `27368`, `27369`, `27370` и `27367`. + +## Концепции деплоя + +Здесь вы увидели, как использовать несколько **воркеров** для **параллелизации** выполнения приложения, использовать преимущества **многих ядер** процессора и иметь возможность обрабатывать **больше запросов**. + +Из списка концепций деплоя, приведённого выше, использование воркеров в основном поможет с **репликацией**, и немного - с **перезапусками**, но вам по-прежнему необходимо позаботиться о следующих вещах: + +* **Безопасность - HTTPS** +* **Запуск при старте** +* ***Перезапуски*** +* Репликация (количество рабочих процессов) +* **Память** +* **Подготовительные шаги перед запуском** + +## Контейнеры и Docker + +В следующей главе о [FastAPI в контейнерах - Docker](docker.md){.internal-link target=_blank} я объясню некоторые стратегии, которые вы можете использовать для работы с другими **концепциями деплоя**. + +Я покажу вам, как **создать собственный образ с нуля** для запуска одного процесса Uvicorn. Это простой процесс и, вероятно, то, что вы захотите сделать при использовании системы управления распределёнными контейнерами, такой как **Kubernetes**. + +## Итог + +Вы можете использовать несколько воркер-процессов с опцией `--workers` в CLI с командами `fastapi` или `uvicorn`, чтобы воспользоваться преимуществами **многоядерных процессоров**, для выполнения **нескольких процессов параллельно**. + +Вы можете использовать эти инструменты и идеи, если настраиваете **собственную систему деплоя**, одновременно заботясь о других концепциях деплоя самостоятельно. + +Изучите следующую главу, чтобы узнать о **FastAPI** с контейнерами (например, Docker и Kubernetes). Вы увидите, что эти инструменты также имеют простые способы решения других **концепций деплоя**. ✨ diff --git a/docs/ru/docs/how-to/conditional-openapi.md b/docs/ru/docs/how-to/conditional-openapi.md new file mode 100644 index 000000000..2ade71806 --- /dev/null +++ b/docs/ru/docs/how-to/conditional-openapi.md @@ -0,0 +1,56 @@ +# Условный OpenAPI + +Если потребуется, вы можете использовать настройки и переменные окружения, чтобы условно настраивать OpenAPI в зависимости от окружения, и даже полностью отключать его. + +## О безопасности, API и документации + +Скрытие пользовательских интерфейсов вашей документации в продакшне *не должно* быть способом защиты вашего API. + +Это не добавляет никакой дополнительной безопасности вашему API, *операции пути* по-прежнему будут доступны там, где они есть. + +Если в вашем коде есть уязвимость, она всё равно будет существовать. + +Скрытие документации лишь затрудняет понимание того, как взаимодействовать с вашим API, и может усложнить его отладку в продакшне. Это может рассматриваться просто как форма безопасности через неясность. + +Если вы хотите защитить ваш API, есть несколько более эффективных подходов: + +* Убедитесь, что у вас есть хорошо определённые Pydantic-модели для тел запросов и ответов. +* Настройте необходимые разрешения и роли с использованием зависимостей. +* Никогда не храните пароли в открытом виде, только хеши паролей. +* Реализуйте и используйте известные криптографические инструменты, такие как Passlib и JWT токены и т. д. +* Добавьте более детальный контроль разрешений с помощью OAuth2 областей, где это необходимо. +* ...и так далее. + +Тем не менее, может быть очень специфический случай, когда вам действительно нужно отключить документацию API для какого-то окружения (например, для продакшна) или в зависимости от настроек из переменных окружения. + +## Условный OpenAPI из настроек и переменных окружения + +Вы можете легко использовать те же настройки Pydantic для конфигурации вашего сгенерированного OpenAPI и интерфейсов документации. + +Например: + +{* ../../docs_src/conditional_openapi/tutorial001.py hl[6,11] *} + +Здесь мы объявляем настройку `openapi_url` с тем же значением по умолчанию `"/openapi.json"`. + +Затем мы используем его при создании приложения `FastAPI`. + +Затем вы можете отключить OpenAPI (включая документацию UI) путем установки значения переменной окружения `OPENAPI_URL` в пустую строку, так: + +
+ +```console +$ OPENAPI_URL= uvicorn main:app + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Затем, если вы перейдете по URL-адресам `/openapi.json`, `/docs` или `/redoc`, вы получите ошибку `404 Not Found`, например: + +```JSON +{ + "detail": "Not Found" +} +``` diff --git a/docs/ru/docs/how-to/configure-swagger-ui.md b/docs/ru/docs/how-to/configure-swagger-ui.md new file mode 100644 index 000000000..d8822b1b1 --- /dev/null +++ b/docs/ru/docs/how-to/configure-swagger-ui.md @@ -0,0 +1,70 @@ +# Настройка Swagger UI + +Вы можете настроить некоторые дополнительные параметры Swagger UI. + +Чтобы их настроить, передайте аргумент `swagger_ui_parameters` при создании объекта приложения `FastAPI()` или в функцию `get_swagger_ui_html()`. + +`swagger_ui_parameters` принимает словарь с конфигурациями, передаваемыми непосредственно в Swagger UI. + +FastAPI преобразует конфигурации в **JSON**, чтобы они были совместимы с JavaScript, так как это нужно для Swagger UI. + +## Отключение подсветки синтаксиса + +Например, вы можете отключить подсветку синтаксиса в Swagger UI. + +Без изменения настроек, подсветка синтаксиса включена по умолчанию: + + + +Но вы можете отключить её, установив `syntaxHighlight` в `False`: + +{* ../../docs_src/configure_swagger_ui/tutorial001.py hl[3] *} + +...и тогда Swagger UI больше не будет отображать подсветку синтаксиса: + + + +## Изменение темы + +Таким же образом вы можете установить тему подсветки синтаксиса с помощью ключа `"syntaxHighlight.theme"` (обратите внимание, что в нем есть точка): + +{* ../../docs_src/configure_swagger_ui/tutorial002.py hl[3] *} + +Эта конфигурация изменит цветовую тему подсветки синтаксиса: + + + +## Изменение параметров Swagger UI по умолчанию + +FastAPI включает некоторые параметры конфигурации по умолчанию, подходящие для большинства случаев использования. + +Эти конфигурации по умолчанию включают: + +{* ../../fastapi/openapi/docs.py ln[8:23] hl[17:23] *} + +Вы можете переопределить любой из них, установив другое значение в аргументе `swagger_ui_parameters`. + +Например, чтобы отключить `deepLinking`, вы можете передать эти настройки в `swagger_ui_parameters`: + +{* ../../docs_src/configure_swagger_ui/tutorial003.py hl[3] *} + +## Другие параметры Swagger UI + +Чтобы узнать о всех возможных конфигурациях, которые вы можете использовать, прочитайте официальную документацию по параметрам Swagger UI. + +## Настройки только для JavaScript + +Swagger UI также позволяет использовать другие конфигурации, представляющие собой **только для JavaScript** объекты (например, функции JavaScript). + +FastAPI также включает эти настройки `presets`, предназначенные только для JavaScript: + +```JavaScript +presets: [ + SwaggerUIBundle.presets.apis, + SwaggerUIBundle.SwaggerUIStandalonePreset +] +``` + +Это объекты **JavaScript**, а не строки, поэтому вы не можете передавать их напрямую из кода Python. + +Если вам нужно использовать такие настройки, предназначенные только для JavaScript, вы можете использовать один из методов выше. Переопределите все *операции пути* Swagger UI и вручную напишите любой нужный JavaScript. diff --git a/docs/ru/docs/how-to/custom-docs-ui-assets.md b/docs/ru/docs/how-to/custom-docs-ui-assets.md new file mode 100644 index 000000000..8887cc57a --- /dev/null +++ b/docs/ru/docs/how-to/custom-docs-ui-assets.md @@ -0,0 +1,185 @@ +# Статические ресурсы пользовательского интерфейса для документации (самостоятельный хостинг) + +Документация API использует **Swagger UI** и **ReDoc**, и для каждой из них нужны определенные JavaScript и CSS файлы. + +По умолчанию эти файлы обслуживаются с CDN. + +Но можно настроить их по-своему, задать определенный CDN или обслуживать файлы самостоятельно. + +## Пользовательский CDN для JavaScript и CSS + +Допустим, вы хотите использовать другой CDN, например, `https://unpkg.com/`. + +Это может быть полезно, если, например, вы живете в стране, где ограничивают некоторые URL-адреса. + +### Отключите автоматическую документацию + +Первый шаг — отключить автоматическую документацию, так как по умолчанию она использует стандартный CDN. + +Чтобы отключить их, установите для их URL-адресов значение `None` при создании приложения `FastAPI`: + +{* ../../docs_src/custom_docs_ui/tutorial001.py hl[8] *} + +### Включите пользовательскую документацию + +Теперь вы можете создать *операции пути* для пользовательской документации. + +Вы можете повторно использовать внутренние функции FastAPI для создания HTML-страниц для документации и передать им необходимые аргументы: + +* `openapi_url`: URL-адрес, где HTML-страница документации может получить OpenAPI-схему для вашего API. Вы можете использовать здесь атрибут `app.openapi_url`. +* `title`: заголовок вашего API. +* `oauth2_redirect_url`: здесь вы можете использовать `app.swagger_ui_oauth2_redirect_url`, чтобы воспользоваться значением по умолчанию. +* `swagger_js_url`: URL-адрес, по которому HTML для ваших документов Swagger UI может получить **JavaScript** файл. Это пользовательский URL-адрес CDN. +* `swagger_css_url`: URL-адрес, по которому HTML для ваших документов Swagger UI может получить **CSS** файл. Это пользовательский URL-адрес CDN. + +И аналогично для ReDoc... + +{* ../../docs_src/custom_docs_ui/tutorial001.py hl[2:6,11:19,22:24,27:33] *} + +/// tip | Совет + +*Операция пути* для `swagger_ui_redirect` — это вспомогательная функция, когда вы используете OAuth2. + +Если вы интегрируете свой API с провайдером OAuth2, вы сможете пройти аутентификацию и вернуться к документации API с полученными учетными данными. И взаимодействовать с ней, используя настоящую аутентификацию OAuth2. + +Swagger UI справится с этим закулисно для вас, но ему нужен этот вспомогательный "редирект". + +/// + +### Создайте *операцию пути* для тестирования + +Теперь, чтобы убедиться, что всё работает, создайте *операцию пути*: + +{* ../../docs_src/custom_docs_ui/tutorial001.py hl[36:38] *} + +### Проверьте это + +Теперь вы должны иметь возможность перейти к вашей документации по адресу http://127.0.0.1:8000/docs и перезагрузить страницу. Она будет загружать эти ресурсы с нового CDN. + +## Самостоятельный хостинг JavaScript и CSS для документации + +Самостоятельный хостинг JavaScript и CSS может быть полезен, если, например, вам нужно, чтобы ваше приложение продолжало работать даже в офлайн-режиме, без открытого доступа к Интернету, или в локальной сети. + +Здесь вы увидите, как можно обслуживать эти файлы самостоятельно, в том же приложении FastAPI, и настроить документацию для их использования. + +### Структура файлов проекта + +Допустим, структура файлов вашего проекта выглядит так: + +``` +. +├── app +│ ├── __init__.py +│ ├── main.py +``` + +Теперь создайте каталог для хранения этих статических файлов. + +Теперь ваша новая структура файлов может выглядеть так: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +└── static/ +``` + +### Загрузите файлы + +Загрузите необходимые статические файлы для документации и разместите их в каталоге `static/`. + +Вы можете щелкнуть правой кнопкой мыши по каждой ссылке и выбрать опцию, аналогичную `Сохранить ссылку как...`. + +**Swagger UI** использует файлы: + +* `swagger-ui-bundle.js` +* `swagger-ui.css` + +А **ReDoc** использует файл: + +* `redoc.standalone.js` + +После этого ваша структура файлов может выглядеть следующим образом: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +└── static + ├── redoc.standalone.js + ├── swagger-ui-bundle.js + └── swagger-ui.css +``` + +### Обслуживание статических файлов + +* Импортируйте `StaticFiles`. +* "Монтируйте" экземпляр `StaticFiles()` на конкретном пути. + +{* ../../docs_src/custom_docs_ui/tutorial002.py hl[7,11] *} + +### Тестирование статических файлов + +Запустите ваше приложение и перейдите на http://127.0.0.1:8000/static/redoc.standalone.js. + +Вы должны увидеть очень длинный JavaScript файл для **ReDoc**. + +Он может начинаться с чего-то вроде: + +```JavaScript +/*! Для информации о лицензии смотрите redoc.standalone.js.LICENSE.txt */ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("null")): +... +``` + +Это подтверждает, что вы можете обслуживать статические файлы из вашего приложения, и что вы разместили статические файлы для документации в правильном месте. + +Теперь мы можем настроить приложение для использования этих статических файлов для документации. + +### Отключите автоматическую документацию для статических файлов + +Также как при использовании пользовательского CDN, первый шаг — отключить автоматическую документацию, так как по умолчанию они используют CDN. + +Чтобы отключить их, установите для их URL-адресов значение `None` при создании приложения `FastAPI`: + +{* ../../docs_src/custom_docs_ui/tutorial002.py hl[9] *} + +### Включите пользовательскую документацию для статических файлов + +И аналогично предыдущим шагам с пользовательским CDN, теперь вы можете создать *операции пути* для пользовательской документации. + +Опять же, вы можете повторно использовать внутренние функции FastAPI для создания HTML-страниц для документации и передать им необходимые аргументы: + +* `openapi_url`: URL-адрес, где HTML-страница документации может получить OpenAPI-схему для вашего API. Вы можете использовать здесь атрибут `app.openapi_url`. +* `title`: заголовок вашего API. +* `oauth2_redirect_url`: здесь вы можете использовать `app.swagger_ui_oauth2_redirect_url`, чтобы воспользоваться значением по умолчанию. +* `swagger_js_url`: URL-адрес, по которому HTML для ваших документов Swagger UI может получить **JavaScript** файл. **Это тот файл, который теперь обслуживает ваше приложение**. +* `swagger_css_url`: URL-адрес, по которому HTML для ваших документов Swagger UI может получить **CSS** файл. **Это тот файл, который теперь обслуживает ваше приложение**. + +И аналогично для ReDoc... + +{* ../../docs_src/custom_docs_ui/tutorial002.py hl[2:6,14:22,25:27,30:36] *} + +/// tip | Совет + +*Операция пути* для `swagger_ui_redirect` — это вспомогательная функция, когда вы используете OAuth2. + +Если вы интегрируете свой API с провайдером OAuth2, вы сможете пройти аутентификацию и вернуться к документации API с полученными учетными данными. И взаимодействовать с ней, используя настоящую аутентификацию OAuth2. + +Swagger UI справится с этим закулисно для вас, но ему нужен этот вспомогательный "редирект". + +/// + +### Создайте *операцию пути* для тестирования статических файлов + +Теперь, чтобы убедиться, что всё работает, создайте *операцию пути*: + +{* ../../docs_src/custom_docs_ui/tutorial002.py hl[39:41] *} + +### Проверьте пользовательский интерфейс статических файлов + +Теперь вы должны иметь возможность отключить WiFi, перейти к вашей документации по адресу http://127.0.0.1:8000/docs и перезагрузить страницу. + +И даже без Интернета вы сможете увидеть документацию для вашего API и взаимодействовать с ней. diff --git a/docs/ru/docs/how-to/custom-request-and-route.md b/docs/ru/docs/how-to/custom-request-and-route.md new file mode 100644 index 000000000..8ade9cf73 --- /dev/null +++ b/docs/ru/docs/how-to/custom-request-and-route.md @@ -0,0 +1,109 @@ +# Пользовательские классы Request и APIRoute + +В некоторых случаях вам может понадобиться переопределить логику, используемую в классах `Request` и `APIRoute`. + +В частности, это может быть хорошей альтернативой логике в middleware (Промежуточный слой). + +Например, если вы хотите прочитать или изменить тело запроса до того, как оно будет обработано вашим приложением. + +/// danger + +Это "продвинутая" функция. + +Если вы только начинаете работать с **FastAPI**, вы можете пропустить этот раздел. + +/// + +## Сценарии использования + +Некоторые сценарии использования включают: + +* Конвертация тел запросов, не являющихся JSON, в JSON (например, `msgpack`). +* Разжатие тел запросов, сжатых методом gzip. +* Автоматическое логирование всех тел запросов. + +## Обработка пользовательских кодировок тела запроса + +Давайте посмотрим, как использовать пользовательский подкласс `Request` для разжатия gzip-запросов. + +И подкласс `APIRoute`, чтобы использовать этот пользовательский класс запроса. + +### Создание пользовательского класса `GzipRequest` + +/// tip | Совет + +Это учебный пример, чтобы показать как это работает. Если вам нужна поддержка gzip, вы можете использовать предоставленный [`GzipMiddleware`](../advanced/middleware.md#gzipmiddleware){.internal-link target=_blank}. + +/// + +Сначала мы создадим класс `GzipRequest`, который переопределит метод `Request.body()`, чтобы разжать тело запроса, если присутствует соответствующий заголовок. + +Если в заголовке нет `gzip`, он не будет пытаться разжать тело запроса. + +Таким образом, один и тот же класс маршрута может обрабатывать как gzip-сжатые, так и несжатые запросы. + +{* ../../docs_src/custom_request_and_route/tutorial001.py hl[8:15] *} + +### Создание пользовательского класса `GzipRoute` + +Затем мы создаем пользовательский подкласс `fastapi.routing.APIRoute`, который будет использовать `GzipRequest`. + +На этот раз он переопределит метод `APIRoute.get_route_handler()`. + +Этот метод возвращает функцию. И эта функция принимает запрос и возвращает ответ. + +Здесь мы используем его для создания `GzipRequest` из исходного запроса. + +{* ../../docs_src/custom_request_and_route/tutorial001.py hl[18:26] *} + +/// note | Технические детали + +`Request` имеет атрибут `request.scope`, который является просто Python-словарем, содержащим метаданные, относящиеся к запросу. + +`Request` также имеет `request.receive`, это функция для "получения" тела запроса. + +Словарь `scope` и функция `receive` являются частью спецификации ASGI. + +И именно эти две вещи, `scope` и `receive`, нужны для создания нового экземпляра `Request`. + +Чтобы узнать больше о `Request`, ознакомьтесь с документацией Starlette о запросах. + +/// + +Единственное, что функция, возвращаемая `GzipRequest.get_route_handler`, делает иначе, это преобразует `Request` в `GzipRequest`. + +Таким образом, наш `GzipRequest` позаботится о разжатии данных (если это необходимо) перед передачей их нашим *операциям пути*. + +После этого вся логика обработки такая же. + +Но благодаря изменениям в `GzipRequest.body`, тело запроса будет автоматически разжиматься, когда **FastAPI** нужно будет его загрузить. + +## Доступ к телу запроса в обработчике исключений + +/// tip | Подсказка + +Чтобы решить эту же проблему, вероятно, гораздо проще использовать `body` в пользовательском обработчике для `RequestValidationError` ([Обработка ошибок](../tutorial/handling-errors.md#use-the-requestvalidationerror-body){.internal-link target=_blank}). + +Но этот пример по-прежнему актуален и показывает, как взаимодействовать с внутренними компонентами. + +/// + +Мы также можем использовать этот же подход, чтобы получить доступ к телу запроса в обработчике исключений. + +Все, что нам нужно сделать, — это обработать запрос внутри блока `try`/`except`: + +{* ../../docs_src/custom_request_and_route/tutorial002.py hl[13,15] *} + +Если возникает исключение, экземпляр `Request` все равно будет доступен, поэтому мы можем прочитать и использовать тело запроса при обработке ошибки: + +{* ../../docs_src/custom_request_and_route/tutorial002.py hl[16:18] *} + +## Пользовательский класс `APIRoute` в роутере + +Вы также можете установить параметр `route_class` для `APIRouter`: + +{* ../../docs_src/custom_request_and_route/tutorial003.py hl[26] *} + +В этом примере *операции путей* под `router` будут использовать пользовательский класс `TimedRoute`, и в ответе будет добавлен дополнительный HTTP-заголовок `X-Response-Time` с временем, затраченным на генерацию ответа: + +{* ../../docs_src/custom_request_and_route/tutorial003.py hl[13:20] *} diff --git a/docs/ru/docs/how-to/extending-openapi.md b/docs/ru/docs/how-to/extending-openapi.md new file mode 100644 index 000000000..d79ef0fdc --- /dev/null +++ b/docs/ru/docs/how-to/extending-openapi.md @@ -0,0 +1,80 @@ +# Расширение OpenAPI + +Бывают случаи, когда вам может понадобиться изменить сгенерированную схему OpenAPI. + +В этом разделе вы узнаете, как это сделать. + +## Обычный процесс + +Обычный (по умолчанию) процесс выглядит следующим образом. + +Приложение `FastAPI` (экземпляр) имеет метод `.openapi()`, который ожидается вернуть схему OpenAPI. + +В рамках создания объекта приложения для `/openapi.json` (или для того, что вы указали в `openapi_url`) регистрируется *операция пути*. + +Она просто возвращает JSON ответ с результатом метода `.openapi()` приложения. + +По умолчанию метод `.openapi()` проверяет свойство `.openapi_schema`, чтобы увидеть, есть ли в нем содержимое, и возвращает их. + +Если его нет, он генерирует его с использованием вспомогательной функции в `fastapi.openapi.utils.get_openapi`. + +И эта функция `get_openapi()` принимает в качестве параметров: + +* `title`: Заголовок OpenAPI, отображаемый в документации. +* `version`: Версия вашего API, например `2.5.0`. +* `openapi_version`: Версия спецификации OpenAPI, используемой. По умолчанию последняя: `3.1.0`. +* `summary`: Краткое резюме API. +* `description`: Описание вашего API, оно может включать markdown и будет показано в документации. +* `routes`: Список маршрутов, это каждая из зарегистрированных *операций пути*. Они берутся из `app.routes`. + +/// info | Информация + +Параметр `summary` доступен в OpenAPI 3.1.0 и выше, поддерживается FastAPI 0.99.0 и выше. + +/// + +## Переопределение значений по умолчанию + +Используя информацию выше, вы можете использовать ту же вспомогательную функцию для генерации схемы OpenAPI и переопределения каждой части, которая вам нужна. + +Например, давайте добавим OpenAPI расширение ReDoc для включения настраиваемого логотипа. + +### Обычный **FastAPI** + +Сначала напишите все ваше приложение на **FastAPI** как обычно: + +{* ../../docs_src/extending_openapi/tutorial001.py hl[1,4,7:9] *} + +### Генерация схемы OpenAPI + +Затем используйте ту же вспомогательную функцию для генерации схемы OpenAPI внутри функции `custom_openapi()`: + +{* ../../docs_src/extending_openapi/tutorial001.py hl[2,15:21] *} + +### Изменение схемы OpenAPI + +Теперь вы можете добавить расширение ReDoc, добавив настраиваемый `x-logo` в "объект" `info` в схеме OpenAPI: + +{* ../../docs_src/extending_openapi/tutorial001.py hl[22:24] *} + +### Кэширование схемы OpenAPI + +Вы можете использовать свойство `.openapi_schema` как "кэш", чтобы сохранить вашу сгенерированную схему. + +Таким образом, ваше приложение не будет генерировать схему каждый раз, когда пользователь открывает вашу документацию API. + +Она будет сгенерирована только один раз, и затем та же самая кэшированная схема будет использована для следующих запросов. + +{* ../../docs_src/extending_openapi/tutorial001.py hl[13:14,25:26] *} + +### Переопределение метода + +Теперь вы можете заменить метод `.openapi()` на вашу новую функцию. + +{* ../../docs_src/extending_openapi/tutorial001.py hl[29] *} + +### Проверьте это + +Как только вы перейдете по адресу http://127.0.0.1:8000/redoc, вы увидите, что вы используете ваш настраиваемый логотип (в этом примере используется логотип **FastAPI**): + + diff --git a/docs/ru/docs/how-to/general.md b/docs/ru/docs/how-to/general.md new file mode 100644 index 000000000..2372fbd74 --- /dev/null +++ b/docs/ru/docs/how-to/general.md @@ -0,0 +1,39 @@ +# Общие сведения - How To - Рецепты + +Вот несколько путеводителей на другие части документации для общих или часто задаваемых вопросов. + +## Фильтрация данных - Безопасность + +Чтобы убедиться, что вы не возвращаете больше данных, чем следует, прочитайте документацию по [Учебнику - Модель ответа - Возвращаемый тип](../tutorial/response-model.md){.internal-link target=_blank}. + +## Теги документации - OpenAPI + +Чтобы добавить теги к вашим *операциям пути* и сгруппировать их в интерфейсе документации, прочитайте документацию по [Учебнику - Конфигурации операций пути - Теги](../tutorial/path-operation-configuration.md#tags){.internal-link target=_blank}. + +## Краткое описание и описание документации - OpenAPI + +Чтобы добавить краткое описание и описание к вашим *операциям пути* и отобразить их в интерфейсе документации, прочитайте документацию по [Учебнику - Конфигурации операций пути - Краткое описание и описание](../tutorial/path-operation-configuration.md#summary-and-description){.internal-link target=_blank}. + +## Описание ответа в документации - OpenAPI + +Чтобы определить описание ответа, отображаемого в интерфейсе документации, прочитайте документацию по [Учебнику - Конфигурации операций пути - Описание ответа](../tutorial/path-operation-configuration.md#response-description){.internal-link target=_blank}. + +## Устаревание *операции пути* в документации - OpenAPI + +Чтобы пометить *операцию пути* как устаревшую и отобразить это в интерфейсе документации, прочитайте документацию по [Учебнику - Конфигурации операций пути - Устаревание](../tutorial/path-operation-configuration.md#deprecate-a-path-operation){.internal-link target=_blank}. + +## Преобразование любых данных в совместимые с JSON + +Чтобы преобразовать любые данные в совместимые с JSON, прочитайте документацию по [Учебнику - Совместимый кодировщик JSON](../tutorial/encoder.md){.internal-link target=_blank}. + +## Метаданные OpenAPI - Документация + +Чтобы добавить метаданные в вашу схему OpenAPI, включая лицензию, версию, контактную информацию и т.д., прочитайте документацию по [Учебнику - Метаданные и URL-адреса документации](../tutorial/metadata.md){.internal-link target=_blank}. + +## Пользовательский URL OpenAPI + +Чтобы кастомизировать (или удалить) URL OpenAPI, прочитайте документацию по [Учебнику - Метаданные и URL-адреса документации](../tutorial/metadata.md#openapi-url){.internal-link target=_blank}. + +## URL-адреса документации OpenAPI + +Чтобы обновить URL-адреса, используемые для автоматически сгенерированных интерфейсов документации, прочитайте документацию по [Учебнику - Метаданные и URL-адреса документации](../tutorial/metadata.md#docs-urls){.internal-link target=_blank}. diff --git a/docs/ru/docs/how-to/graphql.md b/docs/ru/docs/how-to/graphql.md new file mode 100644 index 000000000..5c7fc1c5d --- /dev/null +++ b/docs/ru/docs/how-to/graphql.md @@ -0,0 +1,60 @@ +# GraphQL + +Так как **FastAPI** основан на стандарте **ASGI**, очень легко интегрировать любую библиотеку **GraphQL**, также совместимую с ASGI. + +Вы можете комбинировать обычные *операции пути* FastAPI с GraphQL в одном и том же приложении. + +/// tip | Совет + +**GraphQL** решает некоторые очень специфические случаи использования. + +У него есть **преимущества** и **недостатки** по сравнению с обычными **веб API**. + +Убедитесь, что вы оценили, компенсируют ли **выгоды** для вашего случая использования **недостатки**. 🤓 + +/// + +## Библиотеки GraphQL + +Вот некоторые из библиотек **GraphQL**, которые поддерживают **ASGI**. Вы можете использовать их с **FastAPI**: + +* Strawberry 🍓 + * С документацией для FastAPI +* Ariadne + * С документацией для FastAPI +* Tartiflette + * С Tartiflette ASGI для обеспечения интеграции с ASGI +* Graphene + * С starlette-graphene3 + +## GraphQL с использованием Strawberry + +Если вам нужно или вы хотите работать с **GraphQL**, библиотека **Strawberry** является **рекомендуемой**, так как ее дизайн наиболее близок к дизайну **FastAPI**, и всё основано на **аннотациях типов**. + +В зависимости от вашего случая использования, вы можете предпочесть другую библиотеку, но если бы вы спросили меня, я бы, вероятно, предложил попробовать **Strawberry**. + +Вот небольшой пример того, как можно интегрировать Strawberry с FastAPI: + +{* ../../docs_src/graphql/tutorial001.py hl[3,22,25] *} + +Вы можете узнать больше о Strawberry в документации Strawberry. + +А также документацию о Strawberry с FastAPI. + +## Устаревший `GraphQLApp` от Starlette + +В предыдущих версиях Starlette включалась класс `GraphQLApp` для интеграции с Graphene. + +Он был исключен из Starlette, но если у вас есть код, который использует его, вы можете легко **мигрировать** на starlette-graphene3, который покрывает тот же случай использования и имеет **почти идентичный интерфейс**. + +/// tip | Совет + +Если вам нужен GraphQL, я всё же рекомендую обратить внимание на Strawberry, так как он основан на аннотациях типов, а не на кастомных классах и типах. + +/// + +## Узнать больше + +Вы можете узнать больше о **GraphQL** в официальной документации GraphQL. + +Также вы можете прочитать больше о каждой из описанных выше библиотек по предоставленным ссылкам. diff --git a/docs/ru/docs/how-to/index.md b/docs/ru/docs/how-to/index.md new file mode 100644 index 000000000..7c6db10ab --- /dev/null +++ b/docs/ru/docs/how-to/index.md @@ -0,0 +1,13 @@ +# Как сделать - Рецепты + +Здесь вы найдете различные рецепты или руководства "как сделать" для **различных тем**. + +Большинство этих идей будут более или менее **независимыми**, и в большинстве случаев вам следует изучать их только в том случае, если они напрямую применимы к **вашему проекту**. + +Если что-то кажется интересным и полезным для вашего проекта, смело изучайте это, в противном случае вы можете просто пропустить их. + +/// tip | Совет + +Если вы хотите **изучить FastAPI** структурированным образом (рекомендуется), прочитайте [Учебник - Руководство пользователя](../tutorial/index.md){.internal-link target=_blank} главу за главой. + +/// diff --git a/docs/ru/docs/how-to/separate-openapi-schemas.md b/docs/ru/docs/how-to/separate-openapi-schemas.md new file mode 100644 index 000000000..c970cbc03 --- /dev/null +++ b/docs/ru/docs/how-to/separate-openapi-schemas.md @@ -0,0 +1,104 @@ +# Отдельные схемы OpenAPI для ввода и вывода или нет + +При использовании **Pydantic v2** сгенерированный OpenAPI более точный и **правильный**, чем раньше. 😎 + +Фактически, в некоторых случаях, будет даже **две схемы JSON** в OpenAPI для одной и той же модели Pydantic: для ввода и вывода, в зависимости от наличия **значений по умолчанию**. + +Давайте посмотрим, как это работает и как изменить это, если необходимо. + +## Модели Pydantic для ввода и вывода + +Предположим, у вас есть модель Pydantic со значениями по умолчанию, как эта: + +{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:7] hl[7] *} + +### Модель для ввода + +Если использовать эту модель для ввода, как здесь: + +{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:15] hl[14] *} + +...то поле `description` **не будет обязательным**. Потому что у него есть значение по умолчанию `None`. + +### Модель ввода в документации + +Вы можете убедиться в этом, посмотрев документацию: поле `description` не помечено как обязательное **красной звездочкой**: + +
+ +
+ +### Модель для вывода + +Но если использовать ту же модель для вывода, как здесь: + +{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py hl[19] *} + +...тогда, поскольку `description` имеет значение по умолчанию, если вы **ничего не вернете** для этого поля, оно по-прежнему будет иметь это **значение по умолчанию**. + +### Модель для данных ответа + +Если вы взаимодействуете с документацией и проверяете ответ, даже если в коде ничего не добавлялось в одно из полей `description`, JSON-ответ содержит значение по умолчанию (`null`): + +
+ +
+ +Это означает, что у него **всегда будет значение**, просто иногда это значение может быть `None` (или `null` в JSON). + +Это значит, что клиенты, использующие ваш API, не должны проверять, существует ли значение, они могут **предполагать, что поле всегда будет**, но в некоторых случаях оно будет иметь значение по умолчанию `None`. + +Способ описать это в OpenAPI — отметить это поле как **обязательное**, потому что оно всегда будет присутствовать. + +Из-за этого, схема JSON для модели может отличаться в зависимости от того, используется она для **ввода или вывода**: + +* для **ввода** `description` **не будет обязательным** +* для **вывода** оно будет **обязательным** (и возможно `None`, или в терминах JSON, `null`) + +### Модель для вывода в документации + +Вы также можете проверить модель вывода в документации, **оба** поля `name` и `description` отмечены как **обязательные** с **красной звездочкой**: + +
+ +
+ +### Модель для ввода и вывода в документации + +И если вы проверите все доступные схемы (JSON схемы) в OpenAPI, вы увидите, что их две: `Item-Input` и `Item-Output`. + +Для `Item-Input` `description` **не обязателен**, у него нет красной звездочки. + +Но для `Item-Output` `description` **обязателен**, у него есть красная звездочка. + +
+ +
+ +С этой функцией из **Pydantic v2**, ваша документация API более **точная**, и если у вас есть автоматически сгенерированные клиенты и SDK, они будут более точными тоже, с более лучшим **опытом разработчика** и консистентностью. 🎉 + +## Не разделять схемы + +Теперь, есть случаи, когда вы можете захотеть иметь **одну и ту же схему для ввода и вывода**. + +Возможно, основным случаем использования будет, если у вас уже есть какой-то автоматически сгенерированный клиентский код/SDK, и вы не хотите обновлять весь автоматически сгенерированный клиентский код/SDK еще. Вы, вероятно, захотите сделать это в какой-то момент, но, возможно, не прямо сейчас. + +В этом случае вы можете отключить эту функцию в **FastAPI** с параметром `separate_input_output_schemas=False`. + +/// info | Информация + +Поддержка `separate_input_output_schemas` была добавлена в FastAPI в версии `0.102.0`. 🤓 + +/// + +{* ../../docs_src/separate_openapi_schemas/tutorial002_py310.py hl[10] *} + +### Одинаковая схема для вводных и выводных моделей в документации + +И теперь будет одна единственная схема для ввода и вывода для модели, только `Item`, и `description` будет **необязательным**: + +
+ +
+ +Это такое же поведение, как в Pydantic v1. 🤓 diff --git a/docs/ru/docs/how-to/testing-database.md b/docs/ru/docs/how-to/testing-database.md new file mode 100644 index 000000000..1c674af88 --- /dev/null +++ b/docs/ru/docs/how-to/testing-database.md @@ -0,0 +1,7 @@ +# Тестирование базы данных + +Вы можете изучить базы данных, SQL и SQLModel в документации SQLModel. 🤓 + +Есть мини учебное пособие по использованию SQLModel с FastAPI. ✨ + +Этот учебник включает в себя раздел о тестировании SQL баз данных. 😎 diff --git a/docs/ru/docs/resources/index.md b/docs/ru/docs/resources/index.md new file mode 100644 index 000000000..a04a36e3d --- /dev/null +++ b/docs/ru/docs/resources/index.md @@ -0,0 +1,3 @@ +# Ресурсы + +Дополнительные ресурсы, внешние ссылки, статьи и другое. ✈️