```console
-$ pip install -e ."[dev,doc,test]"
+$ pip install -r requirements.txt
---> 100%
```
diff --git a/docs/ru/docs/contributing.md b/docs/ru/docs/contributing.md
index f61ef1cb6..f9b8912e5 100644
--- a/docs/ru/docs/contributing.md
+++ b/docs/ru/docs/contributing.md
@@ -108,7 +108,7 @@ $ python -m pip install --upgrade pip
```console
-$ pip install -e ."[dev,doc,test]"
+$ pip install -r requirements.txt
---> 100%
```
diff --git a/docs/ru/docs/deployment/concepts.md b/docs/ru/docs/deployment/concepts.md
new file mode 100644
index 000000000..681acf15e
--- /dev/null
+++ b/docs/ru/docs/deployment/concepts.md
@@ -0,0 +1,311 @@
+# Концепции развёртывания
+
+Существует несколько концепций, применяемых для развёртывания приложений **FastAPI**, равно как и для любых других типов веб-приложений, среди которых Вы можете выбрать **наиболее подходящий** способ.
+
+Самые важные из них:
+
+* Использование более безопасного протокола HTTPS
+* Настройки запуска приложения
+* Перезагрузка приложения
+* Запуск нескольких экземпляров приложения
+* Управление памятью
+* Использование перечисленных функций перед запуском приложения.
+
+Рассмотрим ниже влияние каждого из них на процесс **развёртывания**.
+
+Наша конечная цель - **обслуживать клиентов Вашего API безопасно** и **бесперебойно**, с максимально эффективным использованием **вычислительных ресурсов** (например, удалённых серверов/виртуальных машин). 🚀
+
+Здесь я немного расскажу Вам об этих **концепциях** и надеюсь, что у Вас сложится **интуитивное понимание**, какой способ выбрать при развертывании Вашего API в различных окружениях, возможно, даже **ещё не существующих**.
+
+Ознакомившись с этими концепциями, Вы сможете **оценить и выбрать** лучший способ развёртывании **Вашего API**.
+
+В последующих главах я предоставлю Вам **конкретные рецепты** развёртывания приложения FastAPI.
+
+А сейчас давайте остановимся на важных **идеях этих концепций**. Эти идеи можно также применить и к другим типам веб-приложений. 💡
+
+## Использование более безопасного протокола HTTPS
+
+В [предыдущей главе об HTTPS](./https.md){.internal-link target=_blank} мы рассмотрели, как HTTPS обеспечивает шифрование для Вашего API.
+
+Также мы заметили, что обычно для работы с HTTPS Вашему приложению нужен **дополнительный** компонент - **прокси-сервер завершения работы TLS**.
+
+И если прокси-сервер не умеет сам **обновлять сертификаты HTTPS**, то нужен ещё один компонент для этого действия.
+
+### Примеры инструментов для работы с HTTPS
+
+Вот некоторые инструменты, которые Вы можете применять как прокси-серверы:
+
+* Traefik
+ * С автоматическим обновлением сертификатов ✨
+* Caddy
+ * С автоматическим обновлением сертификатов ✨
+* Nginx
+ * С дополнительным компонентом типа Certbot для обновления сертификатов
+* HAProxy
+ * С дополнительным компонентом типа Certbot для обновления сертификатов
+* Kubernetes с Ingress Controller похожим на Nginx
+ * С дополнительным компонентом типа cert-manager для обновления сертификатов
+* Использование услуг облачного провайдера (читайте ниже 👇)
+
+В последнем варианте Вы можете воспользоваться услугами **облачного сервиса**, который сделает большую часть работы, включая настройку HTTPS. Это может наложить дополнительные ограничения или потребовать дополнительную плату и т.п. Зато Вам не понадобится самостоятельно заниматься настройками прокси-сервера.
+
+В дальнейшем я покажу Вам некоторые конкретные примеры их применения.
+
+---
+
+Следующие концепции рассматривают применение программы, запускающей Ваш API (такой как Uvicorn).
+
+## Программа и процесс
+
+Мы часто будем встречать слова **процесс** и **программа**, потому следует уяснить отличия между ними.
+
+### Что такое программа
+
+Термином **программа** обычно описывают множество вещей:
+
+* **Код**, который Вы написали, в нашем случае **Python-файлы**.
+* **Файл**, который может быть **исполнен** операционной системой, например `python`, `python.exe` или `uvicorn`.
+* Конкретная программа, **запущенная** операционной системой и использующая центральный процессор и память. В таком случае это также называется **процесс**.
+
+### Что такое процесс
+
+Термин **процесс** имеет более узкое толкование, подразумевая что-то, запущенное операционной системой (как в последнем пункте из вышестоящего абзаца):
+
+* Конкретная программа, **запущенная** операционной системой.
+ * Это не имеет отношения к какому-либо файлу или коду, но нечто **определённое**, управляемое и **выполняемое** операционной системой.
+* Любая программа, любой код, **могут делать что-то** только когда они **выполняются**. То есть, когда являются **работающим процессом**.
+* Процесс может быть **прерван** (или "убит") Вами или Вашей операционной системой. В результате чего он перестанет исполняться и **не будет продолжать делать что-либо**.
+* Каждое приложение, которое Вы запустили на своём компьютере, каждая программа, каждое "окно" запускает какой-то процесс. И обычно на включенном компьютере **одновременно** запущено множество процессов.
+* И **одна программа** может запустить **несколько параллельных процессов**.
+
+Если Вы заглянете в "диспетчер задач" или "системный монитор" (или аналогичные инструменты) Вашей операционной системы, то увидите множество работающих процессов.
+
+Вполне вероятно, что Вы увидите несколько процессов с одним и тем же названием браузерной программы (Firefox, Chrome, Edge и т. Д.). Обычно браузеры запускают один процесс на вкладку и вдобавок некоторые дополнительные процессы.
+
+

+
+---
+
+Теперь, когда нам известна разница между **процессом** и **программой**, давайте продолжим обсуждение развёртывания.
+
+## Настройки запуска приложения
+
+В большинстве случаев когда Вы создаёте веб-приложение, то желаете, чтоб оно **работало постоянно** и непрерывно, предоставляя клиентам доступ в любое время. Хотя иногда у Вас могут быть причины, чтоб оно запускалось только при определённых условиях.
+
+### Удалённый сервер
+
+Когда Вы настраиваете удалённый сервер (облачный сервер, виртуальную машину и т.п.), самое простое, что можно сделать, запустить Uvicorn (или его аналог) вручную, как Вы делаете при локальной разработке.
+
+Это рабочий способ и он полезен **во время разработки**.
+
+Но если Вы потеряете соединение с сервером, то не сможете отслеживать - работает ли всё ещё **запущенный Вами процесс**.
+
+И если сервер перезагрузится (например, после обновления или каких-то действий облачного провайдера), Вы скорее всего **этого не заметите**, чтобы снова запустить процесс вручную. Вследствие этого Ваш API останется мёртвым. 😱
+
+### Автоматический запуск программ
+
+Вероятно Вы пожелаете, чтоб Ваша серверная программа (такая как Uvicorn) стартовала автоматически при включении сервера, без **человеческого вмешательства** и всегда могла управлять Вашим API (так как Uvicorn запускает приложение FastAPI).
+
+### Отдельная программа
+
+Для этого у обычно используют отдельную программу, которая следит за тем, чтобы Ваши приложения запускались при включении сервера. Такой подход гарантирует, что другие компоненты или приложения также будут запущены, например, база данных
+
+### Примеры инструментов, управляющих запуском программ
+
+Вот несколько примеров, которые могут справиться с такой задачей:
+
+* Docker
+* Kubernetes
+* Docker Compose
+* Docker в режиме Swarm
+* Systemd
+* Supervisor
+* Использование услуг облачного провайдера
+* Прочие...
+
+Я покажу Вам некоторые примеры их использования в следующих главах.
+
+## Перезапуск
+
+Вы, вероятно, также пожелаете, чтоб Ваше приложение **перезапускалось**, если в нём произошёл сбой.
+
+### Мы ошибаемся
+
+Все люди совершают **ошибки**. Программное обеспечение почти *всегда* содержит **баги** спрятавшиеся в разных местах. 🐛
+
+И мы, будучи разработчиками, продолжаем улучшать код, когда обнаруживаем в нём баги или добавляем новый функционал (возможно, добавляя при этом баги 😅).
+
+### Небольшие ошибки обрабатываются автоматически
+
+Когда Вы создаёте свои API на основе FastAPI и допускаете в коде ошибку, то FastAPI обычно остановит её распространение внутри одного запроса, при обработке которого она возникла. 🛡
+
+Клиент получит ошибку **500 Internal Server Error** в ответ на свой запрос, но приложение не сломается и будет продолжать работать с последующими запросами.
+
+### Большие ошибки - Падение приложений
+
+Тем не менее, может случиться так, что ошибка вызовет **сбой всего приложения** или даже сбой в Uvicorn, а то и в самом Python. 💥
+
+Но мы всё ещё хотим, чтобы приложение **продолжало работать** несмотря на эту единственную ошибку, обрабатывая, как минимум, запросы к *операциям пути* не имеющим ошибок.
+
+### Перезапуск после падения
+
+Для случаев, когда ошибки приводят к сбою в запущенном **процессе**, Вам понадобится добавить компонент, который **перезапустит** процесс хотя бы пару раз...
+
+!!! tip "Заметка"
+ ... Если приложение падает сразу же после запуска, вероятно бесполезно его бесконечно перезапускать. Но полагаю, Вы заметите такое поведение во время разработки или, по крайней мере, сразу после развёртывания.
+
+ Так что давайте сосредоточимся на конкретных случаях, когда приложение может полностью выйти из строя, но всё ещё есть смысл его запустить заново.
+
+Возможно Вы захотите, чтоб был некий **внешний компонент**, ответственный за перезапуск Вашего приложения даже если уже не работает Uvicorn или Python. То есть ничего из того, что написано в Вашем коде внутри приложения, не может быть выполнено в принципе.
+
+### Примеры инструментов для автоматического перезапуска
+
+В большинстве случаев инструменты **запускающие программы при старте сервера** умеют **перезапускать** эти программы.
+
+В качестве примера можно взять те же:
+
+* Docker
+* Kubernetes
+* Docker Compose
+* Docker в режиме Swarm
+* Systemd
+* Supervisor
+* Использование услуг облачного провайдера
+* Прочие...
+
+## Запуск нескольких экземпляров приложения (Репликация) - Процессы и память
+
+Приложение FastAPI, управляемое серверной программой (такой как Uvicorn), запускается как **один процесс** и может обслуживать множество клиентов одновременно.
+
+Но часто Вам может понадобиться несколько одновременно работающих одинаковых процессов.
+
+### Множество процессов - Воркеры (Workers)
+
+Если количество Ваших клиентов больше, чем может обслужить один процесс (допустим, что виртуальная машина не слишком мощная), но при этом Вам доступно **несколько ядер процессора**, то Вы можете запустить **несколько процессов** одного и того же приложения параллельно и распределить запросы между этими процессами.
+
+**Несколько запущенных процессов** одной и той же API-программы часто называют **воркерами**.
+
+### Процессы и порты́
+
+Помните ли Вы, как на странице [Об HTTPS](./https.md){.internal-link target=_blank} мы обсуждали, что на сервере только один процесс может слушать одну комбинацию IP-адреса и порта?
+
+С тех пор ничего не изменилось.
+
+Соответственно, чтобы иметь возможность работать с **несколькими процессами** одновременно, должен быть **один процесс, прослушивающий порт** и затем каким-либо образом передающий данные каждому рабочему процессу.
+
+### У каждого процесса своя память
+
+Работающая программа загружает в память данные, необходимые для её работы, например, переменные содержащие модели машинного обучения или большие файлы. Каждая переменная **потребляет некоторое количество оперативной памяти (RAM)** сервера.
+
+Обычно процессы **не делятся памятью друг с другом**. Сие означает, что каждый работающий процесс имеет свои данные, переменные и свой кусок памяти. И если для выполнения Вашего кода процессу нужно много памяти, то **каждый такой же процесс** запущенный дополнительно, потребует такого же количества памяти.
+
+### Память сервера
+
+Допустим, что Ваш код загружает модель машинного обучения **размером 1 ГБ**. Когда Вы запустите своё API как один процесс, он займёт в оперативной памяти не менее 1 ГБ. А если Вы запустите **4 таких же процесса** (4 воркера), то каждый из них займёт 1 ГБ оперативной памяти. В результате Вашему API потребуется **4 ГБ оперативной памяти (RAM)**.
+
+И если Ваш удалённый сервер или виртуальная машина располагает только 3 ГБ памяти, то попытка загрузить в неё 4 ГБ данных вызовет проблемы. 🚨
+
+### Множество процессов - Пример
+
+В этом примере **менеджер процессов** запустит и будет управлять двумя **воркерами**.
+
+Менеджер процессов будет слушать определённый **сокет** (IP:порт) и передавать данные работающим процессам.
+
+Каждый из этих процессов будет запускать Ваше приложение для обработки полученного **запроса** и возвращения вычисленного **ответа** и они будут использовать оперативную память.
+
+

+
+Безусловно, на этом же сервере будут работать и **другие процессы**, которые не относятся к Вашему приложению.
+
+Интересная деталь - обычно в течение времени процент **использования центрального процессора (CPU)** каждым процессом может очень сильно **изменяться**, но объём занимаемой **оперативной памяти (RAM)** остаётся относительно **стабильным**.
+
+Если у Вас есть API, который каждый раз выполняет сопоставимый объем вычислений, и у Вас много клиентов, то **загрузка процессора**, вероятно, *также будет стабильной* (вместо того, чтобы постоянно быстро увеличиваться и уменьшаться).
+
+### Примеры стратегий и инструментов для запуска нескольких экземпляров приложения
+
+Существует несколько подходов для достижения целей репликации и я расскажу Вам больше о конкретных стратегиях в следующих главах, например, когда речь пойдет о Docker и контейнерах.
+
+Основное ограничение при этом - только **один** компонент может работать с определённым **портом публичного IP**. И должен быть способ **передачи** данных между этим компонентом и копиями **процессов/воркеров**.
+
+Вот некоторые возможные комбинации и стратегии:
+
+* **Gunicorn** управляющий **воркерами Uvicorn**
+ * Gunicorn будет выступать как **менеджер процессов**, прослушивая **IP:port**. Необходимое количество запущенных экземпляров приложения будет осуществляться посредством запуска **множества работающих процессов Uvicorn**.
+* **Uvicorn** управляющий **воркерами Uvicorn**
+ * Один процесс Uvicorn будет выступать как **менеджер процессов**, прослушивая **IP:port**. Он будет запускать **множество работающих процессов Uvicorn**.
+* **Kubernetes** и аналогичные **контейнерные системы**
+ * Какой-то компонент в **Kubernetes** будет слушать **IP:port**. Необходимое количество запущенных экземпляров приложения будет осуществляться посредством запуска **нескольких контейнеров**, в каждом из которых работает **один процесс Uvicorn**.
+* **Облачные сервисы**, которые позаботятся обо всём за Вас
+ * Возможно, что облачный сервис умеет **управлять запуском дополнительных экземпляров приложения**. Вероятно, он потребует, чтоб Вы указали - какой **процесс** или **образ** следует клонировать. Скорее всего, Вы укажете **один процесс Uvicorn** и облачный сервис будет запускать его копии при необходимости.
+
+!!! tip "Заметка"
+ Если Вы не знаете, что такое **контейнеры**, Docker или Kubernetes, не переживайте.
+
+ Я поведаю Вам о контейнерах, образах, Docker, Kubernetes и т.п. в главе: [FastAPI внутри контейнеров - Docker](./docker.md){.internal-link target=_blank}.
+
+## Шаги, предшествующие запуску
+
+Часто бывает, что Вам необходимо произвести какие-то подготовительные шаги **перед запуском** своего приложения.
+
+Например, запустить **миграции базы данных**.
+
+Но в большинстве случаев такие действия достаточно произвести **однократно**.
+
+Поэтому Вам нужен будет **один процесс**, выполняющий эти **подготовительные шаги** до запуска приложения.
+
+Также Вам нужно будет убедиться, что этот процесс выполнил подготовительные шаги *даже* если впоследствии Вы запустите **несколько процессов** (несколько воркеров) самого приложения. Если бы эти шаги выполнялись в каждом **клонированном процессе**, они бы **дублировали** работу, пытаясь выполнить её **параллельно**. И если бы эта работа была бы чем-то деликатным, вроде миграции базы данных, то это может вызвать конфликты между ними.
+
+Безусловно, возможны случаи, когда нет проблем при выполнении предварительной подготовки параллельно или несколько раз. Тогда Вам повезло, работать с ними намного проще.
+
+!!! tip "Заметка"
+ Имейте в виду, что в некоторых случаях запуск Вашего приложения **может не требовать каких-либо предварительных шагов вовсе**.
+
+ Что ж, тогда Вам не нужно беспокоиться об этом. 🤷
+
+### Примеры стратегий запуска предварительных шагов
+
+Существует **сильная зависимость** от того, как Вы **развёртываете свою систему**, запускаете программы, обрабатываете перезапуски и т.д.
+
+Вот некоторые возможные идеи:
+
+* При использовании Kubernetes нужно предусмотреть "инициализирующий контейнер", запускаемый до контейнера с приложением.
+* Bash-скрипт, выполняющий предварительные шаги, а затем запускающий приложение.
+ * При этом Вам всё ещё нужно найти способ - как запускать/перезапускать *такой* bash-скрипт, обнаруживать ошибки и т.п.
+
+!!! tip "Заметка"
+ Я приведу Вам больше конкретных примеров работы с контейнерами в главе: [FastAPI внутри контейнеров - Docker](./docker.md){.internal-link target=_blank}.
+
+## Утилизация ресурсов
+
+Ваш сервер располагает ресурсами, которые Ваши программы могут потреблять или **утилизировать**, а именно - время работы центрального процессора и объём оперативной памяти.
+
+Как много системных ресурсов Вы предполагаете потребить/утилизировать? Если не задумываться, то можно ответить - "немного", но на самом деле Вы, вероятно, пожелаете использовать **максимально возможное количество**.
+
+Если Вы платите за содержание трёх серверов, но используете лишь малую часть системных ресурсов каждого из них, то Вы **выбрасываете деньги на ветер**, а также **впустую тратите электроэнергию** и т.п.
+
+В таком случае было бы лучше обойтись двумя серверами, но более полно утилизировать их ресурсы (центральный процессор, оперативную память, жёсткий диск, сети передачи данных и т.д).
+
+С другой стороны, если Вы располагаете только двумя серверами и используете **на 100% их процессоры и память**, но какой-либо процесс запросит дополнительную память, то операционная система сервера будет использовать жёсткий диск для расширения оперативной памяти (а диск работает в тысячи раз медленнее), а то вовсе **упадёт**. Или если какому-то процессу понадобится произвести вычисления, то ему придётся подождать, пока процессор освободится.
+
+В такой ситуации лучше подключить **ещё один сервер** и перераспределить процессы между серверами, чтоб всем **хватало памяти и процессорного времени**.
+
+Также есть вероятность, что по какой-то причине возник **всплеск** запросов к Вашему API. Возможно, это был вирус, боты или другие сервисы начали пользоваться им. И для таких происшествий Вы можете захотеть иметь дополнительные ресурсы.
+
+При настройке логики развёртываний, Вы можете указать **целевое значение** утилизации ресурсов, допустим, **от 50% до 90%**. Обычно эти метрики и используют.
+
+Вы можете использовать простые инструменты, такие как `htop`, для отслеживания загрузки центрального процессора и оперативной памяти сервера, в том числе каждым процессом. Или более сложные системы мониторинга нескольких серверов.
+
+## Резюме
+
+Вы прочитали некоторые из основных концепций, которые необходимо иметь в виду при принятии решения о развертывании приложений:
+
+* Использование более безопасного протокола HTTPS
+* Настройки запуска приложения
+* Перезагрузка приложения
+* Запуск нескольких экземпляров приложения
+* Управление памятью
+* Использование перечисленных функций перед запуском приложения.
+
+Осознание этих идей и того, как их применять, должно дать Вам интуитивное понимание, необходимое для принятия решений при настройке развертываний. 🤓
+
+В следующих разделах я приведу более конкретные примеры возможных стратегий, которым Вы можете следовать. 🚀
diff --git a/docs/ru/docs/tutorial/body-multiple-params.md b/docs/ru/docs/tutorial/body-multiple-params.md
new file mode 100644
index 000000000..a20457092
--- /dev/null
+++ b/docs/ru/docs/tutorial/body-multiple-params.md
@@ -0,0 +1,309 @@
+# Body - Множество параметров
+
+Теперь, когда мы увидели, как использовать `Path` и `Query` параметры, давайте рассмотрим более продвинутые примеры обьявления тела запроса.
+
+## Обьединение `Path`, `Query` и параметров тела запроса
+
+Во-первых, конечно, вы можете объединять параметры `Path`, `Query` и объявления тела запроса в своих функциях обработки, **FastAPI** автоматически определит, что с ними нужно делать.
+
+Вы также можете объявить параметры тела запроса как необязательные, установив значение по умолчанию, равное `None`:
+
+=== "Python 3.10+"
+
+ ```Python hl_lines="18-20"
+ {!> ../../../docs_src/body_multiple_params/tutorial001_an_py310.py!}
+ ```
+
+=== "Python 3.9+"
+
+ ```Python hl_lines="18-20"
+ {!> ../../../docs_src/body_multiple_params/tutorial001_an_py39.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="19-21"
+ {!> ../../../docs_src/body_multiple_params/tutorial001_an.py!}
+ ```
+
+=== "Python 3.10+ non-Annotated"
+
+ !!! Заметка
+ Рекомендуется использовать `Annotated` версию, если это возможно.
+
+ ```Python hl_lines="17-19"
+ {!> ../../../docs_src/body_multiple_params/tutorial001_py310.py!}
+ ```
+
+=== "Python 3.6+ non-Annotated"
+
+ !!! Заметка
+ Рекомендуется использовать версию с `Annotated`, если это возможно.
+
+ ```Python hl_lines="19-21"
+ {!> ../../../docs_src/body_multiple_params/tutorial001.py!}
+ ```
+
+!!! Заметка
+ Заметьте, что в данном случае параметр `item`, который будет взят из тела запроса, необязателен. Так как было установлено значение `None` по умолчанию.
+
+## Несколько параметров тела запроса
+
+В предыдущем примере, *операции пути* ожидали тело запроса в формате JSON-тело с параметрами, соответствующими атрибутам `Item`, например:
+
+```JSON
+{
+ "name": "Foo",
+ "description": "The pretender",
+ "price": 42.0,
+ "tax": 3.2
+}
+```
+
+Но вы также можете объявить множество параметров тела запроса, например `item` и `user`:
+
+=== "Python 3.10+"
+
+ ```Python hl_lines="20"
+ {!> ../../../docs_src/body_multiple_params/tutorial002_py310.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="22"
+ {!> ../../../docs_src/body_multiple_params/tutorial002.py!}
+ ```
+
+В этом случае **FastAPI** заметит, что в функции есть более одного параметра тела (два параметра, которые являются моделями Pydantic).
+
+Таким образом, имена параметров будут использоваться в качестве ключей (имён полей) в теле запроса, и будет ожидаться запрос следующего формата:
+
+```JSON
+{
+ "item": {
+ "name": "Foo",
+ "description": "The pretender",
+ "price": 42.0,
+ "tax": 3.2
+ },
+ "user": {
+ "username": "dave",
+ "full_name": "Dave Grohl"
+ }
+}
+```
+
+!!! Внимание
+ Обратите внимание, что хотя параметр `item` был объявлен таким же способом, как и раньше, теперь предпологается, что он находится внутри тела с ключом `item`.
+
+
+**FastAPI** сделает автоматические преобразование из запроса, так что параметр `item` получит своё конкретное содержимое, и то же самое происходит с пользователем `user`.
+
+Произойдёт проверка составных данных, и создание документации в схеме OpenAPI и автоматических документах.
+
+## Отдельные значения в теле запроса
+
+Точно так же, как `Query` и `Path` используются для определения дополнительных данных для query и path параметров, **FastAPI** предоставляет аналогичный инструмент - `Body`.
+
+Например, расширяя предыдущую модель, вы можете решить, что вам нужен еще один ключ `importance` в том же теле запроса, помимо параметров `item` и `user`.
+
+Если вы объявите его без указания, какой именно объект (Path, Query, Body и .т.п.) ожидаете, то, поскольку это является простым типом данных, **FastAPI** будет считать, что это query-параметр.
+
+Но вы можете указать **FastAPI** обрабатывать его, как ещё один ключ тела запроса, используя `Body`:
+
+=== "Python 3.10+"
+
+ ```Python hl_lines="23"
+ {!> ../../../docs_src/body_multiple_params/tutorial003_an_py310.py!}
+ ```
+
+=== "Python 3.9+"
+
+ ```Python hl_lines="23"
+ {!> ../../../docs_src/body_multiple_params/tutorial003_an_py39.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="24"
+ {!> ../../../docs_src/body_multiple_params/tutorial003_an.py!}
+ ```
+
+=== "Python 3.10+ non-Annotated"
+
+ !!! Заметка
+ Рекомендуется использовать `Annotated` версию, если это возможно.
+
+ ```Python hl_lines="20"
+ {!> ../../../docs_src/body_multiple_params/tutorial003_py310.py!}
+ ```
+
+=== "Python 3.6+ non-Annotated"
+
+ !!! Заметка
+ Рекомендуется использовать `Annotated` версию, если это возможно.
+
+ ```Python hl_lines="22"
+ {!> ../../../docs_src/body_multiple_params/tutorial003.py!}
+ ```
+
+В этом случае, **FastAPI** будет ожидать тело запроса в формате:
+
+```JSON
+{
+ "item": {
+ "name": "Foo",
+ "description": "The pretender",
+ "price": 42.0,
+ "tax": 3.2
+ },
+ "user": {
+ "username": "dave",
+ "full_name": "Dave Grohl"
+ },
+ "importance": 5
+}
+```
+
+И всё будет работать так же - преобразование типов данных, валидация, документирование и т.д.
+
+## Множество body и query параметров
+
+Конечно, вы также можете объявлять query-параметры в любое время, дополнительно к любым body-параметрам.
+
+Поскольку по умолчанию, отдельные значения интерпретируются как query-параметры, вам не нужно явно добавлять `Query`, вы можете просто сделать так:
+
+```Python
+q: Union[str, None] = None
+```
+
+Или в Python 3.10 и выше:
+
+```Python
+q: str | None = None
+```
+
+Например:
+
+=== "Python 3.10+"
+
+ ```Python hl_lines="27"
+ {!> ../../../docs_src/body_multiple_params/tutorial004_an_py310.py!}
+ ```
+
+=== "Python 3.9+"
+
+ ```Python hl_lines="27"
+ {!> ../../../docs_src/body_multiple_params/tutorial004_an_py39.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="28"
+ {!> ../../../docs_src/body_multiple_params/tutorial004_an.py!}
+ ```
+
+=== "Python 3.10+ non-Annotated"
+
+ !!! Заметка
+ Рекомендуется использовать `Annotated` версию, если это возможно.
+
+ ```Python hl_lines="25"
+ {!> ../../../docs_src/body_multiple_params/tutorial004_py310.py!}
+ ```
+
+=== "Python 3.6+ non-Annotated"
+
+ !!! Заметка
+ Рекомендуется использовать `Annotated` версию, если это возможно.
+
+ ```Python hl_lines="27"
+ {!> ../../../docs_src/body_multiple_params/tutorial004.py!}
+ ```
+
+!!! Информация
+ `Body` также имеет все те же дополнительные параметры валидации и метаданных, как у `Query`,`Path` и других, которые вы увидите позже.
+
+## Добавление одного body-параметра
+
+Предположим, у вас есть только один body-параметр `item` из Pydantic модели `Item`.
+
+По умолчанию, **FastAPI** ожидает получить тело запроса напрямую.
+
+Но если вы хотите чтобы он ожидал JSON с ключом `item` с содержимым модели внутри, также как это происходит при объявлении дополнительных body-параметров, вы можете использовать специальный параметр `embed` у типа `Body`:
+
+```Python
+item: Item = Body(embed=True)
+```
+
+так же, как в этом примере:
+
+=== "Python 3.10+"
+
+ ```Python hl_lines="17"
+ {!> ../../../docs_src/body_multiple_params/tutorial005_an_py310.py!}
+ ```
+
+=== "Python 3.9+"
+
+ ```Python hl_lines="17"
+ {!> ../../../docs_src/body_multiple_params/tutorial005_an_py39.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="18"
+ {!> ../../../docs_src/body_multiple_params/tutorial005_an.py!}
+ ```
+
+=== "Python 3.10+ non-Annotated"
+
+ !!! Заметка
+ Рекомендуется использовать `Annotated` версию, если это возможно.
+
+ ```Python hl_lines="15"
+ {!> ../../../docs_src/body_multiple_params/tutorial005_py310.py!}
+ ```
+
+=== "Python 3.6+ non-Annotated"
+
+ !!! Заметка
+ Рекомендуется использовать `Annotated` версию, если это возможно.
+
+ ```Python hl_lines="17"
+ {!> ../../../docs_src/body_multiple_params/tutorial005.py!}
+ ```
+
+В этом случае **FastAPI** будет ожидать тело запроса в формате:
+
+```JSON hl_lines="2"
+{
+ "item": {
+ "name": "Foo",
+ "description": "The pretender",
+ "price": 42.0,
+ "tax": 3.2
+ }
+}
+```
+
+вместо этого:
+
+```JSON
+{
+ "name": "Foo",
+ "description": "The pretender",
+ "price": 42.0,
+ "tax": 3.2
+}
+```
+
+## Резюме
+
+Вы можете добавлять несколько body-параметров вашей *функции операции пути*, несмотря даже на то, что запрос может содержать только одно тело.
+
+Но **FastAPI** справится с этим, предоставит правильные данные в вашей функции, а также сделает валидацию и документацию правильной схемы *операции пути*.
+
+Вы также можете объявить отдельные значения для получения в рамках тела запроса.
+
+И вы можете настроить **FastAPI** таким образом, чтобы включить тело запроса в ключ, даже если объявлен только один параметр.
diff --git a/docs/ru/docs/tutorial/body-nested-models.md b/docs/ru/docs/tutorial/body-nested-models.md
new file mode 100644
index 000000000..6435e316f
--- /dev/null
+++ b/docs/ru/docs/tutorial/body-nested-models.md
@@ -0,0 +1,382 @@
+# Body - Вложенные модели
+
+С помощью **FastAPI**, вы можете определять, валидировать, документировать и использовать модели произвольной вложенности (благодаря библиотеке Pydantic).
+
+## Определение полей содержащих списки
+
+Вы можете определять атрибут как подтип. Например, тип `list` в Python:
+
+=== "Python 3.10+"
+
+ ```Python hl_lines="12"
+ {!> ../../../docs_src/body_nested_models/tutorial001_py310.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="14"
+ {!> ../../../docs_src/body_nested_models/tutorial001.py!}
+ ```
+
+Это приведёт к тому, что обьект `tags` преобразуется в список, несмотря на то что тип его элементов не объявлен.
+
+## Определение полей содержащих список с определением типов его элементов
+
+Однако в Python есть способ объявления списков с указанием типов для вложенных элементов:
+
+### Импортируйте `List` из модуля typing
+
+В Python 3.9 и выше вы можете использовать стандартный тип `list` для объявления аннотаций типов, как мы увидим ниже. 💡
+
+Но в версиях Python до 3.9 (начиная с 3.6) сначала вам необходимо импортировать `List` из стандартного модуля `typing` в Python:
+
+```Python hl_lines="1"
+{!> ../../../docs_src/body_nested_models/tutorial002.py!}
+```
+
+### Объявление `list` с указанием типов для вложенных элементов
+
+Объявление типов для элементов (внутренних типов) вложенных в такие типы как `list`, `dict`, `tuple`:
+
+* Если у вас Python версии ниже чем 3.9, импортируйте их аналог из модуля `typing`
+* Передайте внутренний(ие) тип(ы) как "параметры типа", используя квадратные скобки: `[` и `]`
+
+В Python версии 3.9 это будет выглядеть так:
+
+```Python
+my_list: list[str]
+```
+
+В версиях Python до 3.9 это будет выглядеть так:
+
+```Python
+from typing import List
+
+my_list: List[str]
+```
+
+Это всё стандартный синтаксис Python для объявления типов.
+
+Используйте этот же стандартный синтаксис для атрибутов модели с внутренними типами.
+
+Таким образом, в нашем примере мы можем явно указать тип данных для поля `tags` как "список строк":
+
+=== "Python 3.10+"
+
+ ```Python hl_lines="12"
+ {!> ../../../docs_src/body_nested_models/tutorial002_py310.py!}
+ ```
+
+=== "Python 3.9+"
+
+ ```Python hl_lines="14"
+ {!> ../../../docs_src/body_nested_models/tutorial002_py39.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="14"
+ {!> ../../../docs_src/body_nested_models/tutorial002.py!}
+ ```
+
+## Типы множеств
+
+Но затем мы подумали и поняли, что теги не должны повторяться и, вероятно, они должны быть уникальными строками.
+
+И в Python есть специальный тип данных для множеств уникальных элементов - `set`.
+
+Тогда мы может обьявить поле `tags` как множество строк:
+
+=== "Python 3.10+"
+
+ ```Python hl_lines="12"
+ {!> ../../../docs_src/body_nested_models/tutorial003_py310.py!}
+ ```
+
+=== "Python 3.9+"
+
+ ```Python hl_lines="14"
+ {!> ../../../docs_src/body_nested_models/tutorial003_py39.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="1 14"
+ {!> ../../../docs_src/body_nested_models/tutorial003.py!}
+ ```
+
+С помощью этого, даже если вы получите запрос с повторяющимися данными, они будут преобразованы в множество уникальных элементов.
+
+И когда вы выводите эти данные, даже если исходный набор содержал дубликаты, они будут выведены в виде множества уникальных элементов.
+
+И они также будут соответствующим образом аннотированы / задокументированы.
+
+## Вложенные Модели
+
+У каждого атрибута Pydantic-модели есть тип.
+
+Но этот тип может сам быть другой моделью Pydantic.
+
+Таким образом вы можете объявлять глубоко вложенные JSON "объекты" с определёнными именами атрибутов, типами и валидацией.
+
+Всё это может быть произвольно вложенным.
+
+### Определение подмодели
+
+Например, мы можем определить модель `Image`:
+
+=== "Python 3.10+"
+
+ ```Python hl_lines="7-9"
+ {!> ../../../docs_src/body_nested_models/tutorial004_py310.py!}
+ ```
+
+=== "Python 3.9+"
+
+ ```Python hl_lines="9-11"
+ {!> ../../../docs_src/body_nested_models/tutorial004_py39.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="9-11"
+ {!> ../../../docs_src/body_nested_models/tutorial004.py!}
+ ```
+
+### Использование вложенной модели в качестве типа
+
+Также мы можем использовать эту модель как тип атрибута:
+
+=== "Python 3.10+"
+
+ ```Python hl_lines="18"
+ {!> ../../../docs_src/body_nested_models/tutorial004_py310.py!}
+ ```
+
+=== "Python 3.9+"
+
+ ```Python hl_lines="20"
+ {!> ../../../docs_src/body_nested_models/tutorial004_py39.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="20"
+ {!> ../../../docs_src/body_nested_models/tutorial004.py!}
+ ```
+
+Это означает, что **FastAPI** будет ожидать тело запроса, аналогичное этому:
+
+```JSON
+{
+ "name": "Foo",
+ "description": "The pretender",
+ "price": 42.0,
+ "tax": 3.2,
+ "tags": ["rock", "metal", "bar"],
+ "image": {
+ "url": "http://example.com/baz.jpg",
+ "name": "The Foo live"
+ }
+}
+```
+
+Ещё раз: сделав такое объявление, с помощью **FastAPI** вы получите:
+
+* Поддержку редакторов IDE (автодополнение и т.д), даже для вложенных моделей
+* Преобразование данных
+* Валидацию данных
+* Автоматическую документацию
+
+## Особые типы и валидация
+
+Помимо обычных простых типов, таких как `str`, `int`, `float`, и т.д. Вы можете использовать более сложные базовые типы, которые наследуются от типа `str`.
+
+Чтобы увидеть все варианты, которые у вас есть, ознакомьтесь с документацией
по необычным типам Pydantic. Вы увидите некоторые примеры в следующей главе.
+
+Например, так как в модели `Image` у нас есть поле `url`, то мы можем объявить его как тип `HttpUrl` из модуля Pydantic вместо типа `str`:
+
+=== "Python 3.10+"
+
+ ```Python hl_lines="2 8"
+ {!> ../../../docs_src/body_nested_models/tutorial005_py310.py!}
+ ```
+
+=== "Python 3.9+"
+
+ ```Python hl_lines="4 10"
+ {!> ../../../docs_src/body_nested_models/tutorial005_py39.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="4 10"
+ {!> ../../../docs_src/body_nested_models/tutorial005.py!}
+ ```
+
+Строка будет проверена на соответствие допустимому URL-адресу и задокументирована в JSON схему / OpenAPI.
+
+## Атрибуты, содержащие списки подмоделей
+
+Вы также можете использовать модели Pydantic в качестве типов вложенных в `list`, `set` и т.д:
+
+=== "Python 3.10+"
+
+ ```Python hl_lines="18"
+ {!> ../../../docs_src/body_nested_models/tutorial006_py310.py!}
+ ```
+
+=== "Python 3.9+"
+
+ ```Python hl_lines="20"
+ {!> ../../../docs_src/body_nested_models/tutorial006_py39.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="20"
+ {!> ../../../docs_src/body_nested_models/tutorial006.py!}
+ ```
+
+Такая реализация будет ожидать (конвертировать, валидировать, документировать и т.д) JSON-содержимое в следующем формате:
+
+```JSON hl_lines="11"
+{
+ "name": "Foo",
+ "description": "The pretender",
+ "price": 42.0,
+ "tax": 3.2,
+ "tags": [
+ "rock",
+ "metal",
+ "bar"
+ ],
+ "images": [
+ {
+ "url": "http://example.com/baz.jpg",
+ "name": "The Foo live"
+ },
+ {
+ "url": "http://example.com/dave.jpg",
+ "name": "The Baz"
+ }
+ ]
+}
+```
+
+!!! info "Информация"
+ Заметьте, что теперь у ключа `images` есть список объектов изображений.
+
+## Глубоко вложенные модели
+
+Вы можете определять модели с произвольным уровнем вложенности:
+
+=== "Python 3.10+"
+
+ ```Python hl_lines="7 12 18 21 25"
+ {!> ../../../docs_src/body_nested_models/tutorial007_py310.py!}
+ ```
+
+=== "Python 3.9+"
+
+ ```Python hl_lines="9 14 20 23 27"
+ {!> ../../../docs_src/body_nested_models/tutorial007_py39.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="9 14 20 23 27"
+ {!> ../../../docs_src/body_nested_models/tutorial007.py!}
+ ```
+
+!!! info "Информация"
+ Заметьте, что у объекта `Offer` есть список объектов `Item`, которые, в свою очередь, могут содержать необязательный список объектов `Image`
+
+## Тела с чистыми списками элементов
+
+Если верхний уровень значения тела JSON-объекта представляет собой JSON `array` (в Python - `list`), вы можете объявить тип в параметре функции, так же, как в моделях Pydantic:
+
+```Python
+images: List[Image]
+```
+
+в Python 3.9 и выше:
+
+```Python
+images: list[Image]
+```
+
+например так:
+
+=== "Python 3.9+"
+
+ ```Python hl_lines="13"
+ {!> ../../../docs_src/body_nested_models/tutorial008_py39.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="15"
+ {!> ../../../docs_src/body_nested_models/tutorial008.py!}
+ ```
+
+## Универсальная поддержка редактора
+
+И вы получаете поддержку редактора везде.
+
+Даже для элементов внутри списков:
+
+

+
+Вы не могли бы получить такую поддержку редактора, если бы работали напрямую с `dict`, а не с моделями Pydantic.
+
+Но вы также не должны беспокоиться об этом, входящие словари автоматически конвертируются, а ваш вывод также автоматически преобразуется в формат JSON.
+
+## Тела запросов с произвольными словарями (`dict` )
+
+Вы также можете объявить тело запроса как `dict` с ключами определенного типа и значениями другого типа данных.
+
+Без необходимости знать заранее, какие значения являются допустимыми для имён полей/атрибутов (как это было бы в случае с моделями Pydantic).
+
+Это было бы полезно, если вы хотите получить ключи, которые вы еще не знаете.
+
+---
+
+Другой полезный случай - когда вы хотите чтобы ключи были другого типа данных, например, `int`.
+
+Именно это мы сейчас и увидим здесь.
+
+В этом случае вы принимаете `dict`, пока у него есть ключи типа `int` со значениями типа `float`:
+
+=== "Python 3.9+"
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/body_nested_models/tutorial009_py39.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/body_nested_models/tutorial009.py!}
+ ```
+
+!!! tip "Совет"
+ Имейте в виду, что JSON поддерживает только ключи типа `str`.
+
+ Но Pydantic обеспечивает автоматическое преобразование данных.
+
+ Это значит, что даже если пользователи вашего API могут отправлять только строки в качестве ключей, при условии, что эти строки содержат целые числа, Pydantic автоматический преобразует и валидирует эти данные.
+
+ А `dict`, с именем `weights`, который вы получите в качестве ответа Pydantic, действительно будет иметь ключи типа `int` и значения типа `float`.
+
+## Резюме
+
+С помощью **FastAPI** вы получаете максимальную гибкость, предоставляемую моделями Pydantic, сохраняя при этом простоту, краткость и элегантность вашего кода.
+
+И дополнительно вы получаете:
+
+* Поддержку редактора (автодополнение доступно везде!)
+* Преобразование данных (также известно как парсинг / сериализация)
+* Валидацию данных
+* Документацию схемы данных
+* Автоматическую генерацию документации
diff --git a/docs/ru/docs/tutorial/body.md b/docs/ru/docs/tutorial/body.md
new file mode 100644
index 000000000..c03d40c3f
--- /dev/null
+++ b/docs/ru/docs/tutorial/body.md
@@ -0,0 +1,165 @@
+# Тело запроса
+
+Когда вам необходимо отправить данные из клиента (допустим, браузера) в ваш API, вы отправляете их как **тело запроса**.
+
+Тело **запроса** --- это данные, отправляемые клиентом в ваш API. Тело **ответа** --- это данные, которые ваш API отправляет клиенту.
+
+Ваш API почти всегда отправляет тело **ответа**. Но клиентам не обязательно всегда отправлять тело **запроса**.
+
+Чтобы объявить тело **запроса**, необходимо использовать модели
Pydantic, со всей их мощью и преимуществами.
+
+!!! info "Информация"
+ Чтобы отправить данные, необходимо использовать один из методов: `POST` (обычно), `PUT`, `DELETE` или `PATCH`.
+
+ Отправка тела с запросом `GET` имеет неопределенное поведение в спецификациях, тем не менее, оно поддерживается FastAPI только для очень сложных/экстремальных случаев использования.
+
+ Поскольку это не рекомендуется, интерактивная документация со Swagger UI не будет отображать информацию для тела при использовании метода GET, а промежуточные прокси-серверы могут не поддерживать такой вариант запроса.
+
+## Импортирование `BaseModel` из Pydantic
+
+Первое, что вам необходимо сделать, это импортировать `BaseModel` из пакета `pydantic`:
+
+```Python hl_lines="4"
+{!../../../docs_src/body/tutorial001.py!}
+```
+
+## Создание вашей собственной модели
+
+После этого вы описываете вашу модель данных как класс, наследующий от `BaseModel`.
+
+Используйте аннотации типов Python для всех атрибутов:
+
+```Python hl_lines="7-11"
+{!../../../docs_src/body/tutorial001.py!}
+```
+
+Также как и при описании параметров запроса, когда атрибут модели имеет значение по умолчанию, он является необязательным. Иначе он обязателен. Используйте `None`, чтобы сделать его необязательным без использования конкретных значений по умолчанию.
+
+Например, модель выше описывает вот такой JSON "объект" (или словарь Python):
+
+```JSON
+{
+ "name": "Foo",
+ "description": "An optional description",
+ "price": 45.2,
+ "tax": 3.5
+}
+```
+
+...поскольку `description` и `tax` являются необязательными (с `None` в качестве значения по умолчанию), вот такой JSON "объект" также подходит:
+
+```JSON
+{
+ "name": "Foo",
+ "price": 45.2
+}
+```
+
+## Объявление как параметра функции
+
+Чтобы добавить параметр к вашему *обработчику*, объявите его также, как вы объявляли параметры пути или параметры запроса:
+
+```Python hl_lines="18"
+{!../../../docs_src/body/tutorial001.py!}
+```
+
+...и укажите созданную модель в качестве типа параметра, `Item`.
+
+## Результаты
+
+Всего лишь с помощью аннотации типов Python, **FastAPI**:
+
+* Читает тело запроса как JSON.
+* Приводит к соответствующим типам (если есть необходимость).
+* Проверяет корректность данных.
+ * Если данные некорректны, будет возращена читаемая и понятная ошибка, показывающая что именно и в каком месте некорректно в данных.
+* Складывает полученные данные в параметр `item`.
+ * Поскольку внутри функции вы объявили его с типом `Item`, то теперь у вас есть поддержка со стороны редактора (автодополнение и т.п.) для всех атрибутов и их типов.
+* Генерирует декларативное описание модели в виде
JSON Schema, так что вы можете его использовать где угодно, если это имеет значение для вашего проекта.
+* Эти схемы являются частью сгенерированной схемы OpenAPI и используются для автоматического документирования
UI.
+
+## Автоматическое документирование
+
+Схема JSON ваших моделей будет частью сгенерированной схемы OpenAPI и будет отображена в интерактивной документации API:
+
+

+
+Также она будет указана в документации по API внутри каждой *операции пути*, в которой используются:
+
+

+
+## Поддержка редактора
+
+В вашем редакторе внутри вашей функции у вас будут подсказки по типам и автодополнение (это не будет работать, если вы получаете словарь вместо модели Pydantic):
+
+

+
+Также вы будете получать ошибки в случае несоответствия типов:
+
+

+
+Это не случайно, весь фреймворк построен вокруг такого дизайна.
+
+И это все тщательно протестировано еще на этапе разработки дизайна, до реализации, чтобы это работало со всеми редакторами.
+
+Для поддержки этого даже были внесены некоторые изменения в сам Pydantic.
+
+На всех предыдущих скриншотах используется
Visual Studio Code.
+
+Но у вас будет такая же поддержка и с
PyCharm, и вообще с любым редактором Python:
+
+

+
+!!! tip "Подсказка"
+ Если вы используете
PyCharm в качестве редактора, то вам стоит попробовать плагин
Pydantic PyCharm Plugin.
+
+ Он улучшает поддержку редактором моделей Pydantic в части:
+
+ * автодополнения,
+ * проверки типов,
+ * рефакторинга,
+ * поиска,
+ * инспектирования.
+
+## Использование модели
+
+Внутри функции вам доступны все атрибуты объекта модели напрямую:
+
+```Python hl_lines="21"
+{!../../../docs_src/body/tutorial002.py!}
+```
+
+## Тело запроса + параметры пути
+
+Вы можете одновременно объявлять параметры пути и тело запроса.
+
+**FastAPI** распознает, какие параметры функции соответствуют параметрам пути и должны быть **получены из пути**, а какие параметры функции, объявленные как модели Pydantic, должны быть **получены из тела запроса**.
+
+```Python hl_lines="17-18"
+{!../../../docs_src/body/tutorial003.py!}
+```
+
+## Тело запроса + параметры пути + параметры запроса
+
+Вы также можете одновременно объявить параметры для **пути**, **запроса** и **тела запроса**.
+
+**FastAPI** распознает каждый из них и возьмет данные из правильного источника.
+
+```Python hl_lines="18"
+{!../../../docs_src/body/tutorial004.py!}
+```
+
+Параметры функции распознаются следующим образом:
+
+* Если параметр также указан в **пути**, то он будет использоваться как параметр пути.
+* Если аннотация типа параметра содержит **примитивный тип** (`int`, `float`, `str`, `bool` и т.п.), он будет интерпретирован как параметр **запроса**.
+* Если аннотация типа параметра представляет собой **модель Pydantic**, он будет интерпретирован как параметр **тела запроса**.
+
+!!! note "Заметка"
+ FastAPI понимает, что значение параметра `q` не является обязательным, потому что имеет значение по умолчанию `= None`.
+
+ Аннотация `Optional` в `Optional[str]` не используется FastAPI, но помогает вашему редактору лучше понимать ваш код и обнаруживать ошибки.
+
+## Без Pydantic
+
+Если вы не хотите использовать модели Pydantic, вы все еще можете использовать параметры **тела запроса**. Читайте в документации раздел [Тело - Несколько параметров: Единичные значения в теле](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}.
diff --git a/docs/ru/docs/tutorial/debugging.md b/docs/ru/docs/tutorial/debugging.md
new file mode 100644
index 000000000..755d98cf2
--- /dev/null
+++ b/docs/ru/docs/tutorial/debugging.md
@@ -0,0 +1,112 @@
+# Отладка
+
+Вы можете подключить отладчик в своем редакторе, например, в Visual Studio Code или PyCharm.
+
+## Вызов `uvicorn`
+
+В вашем FastAPI приложении, импортируйте и вызовите `uvicorn` напрямую:
+
+```Python hl_lines="1 15"
+{!../../../docs_src/debugging/tutorial001.py!}
+```
+
+### Описание `__name__ == "__main__"`
+
+Главная цель использования `__name__ == "__main__"` в том, чтобы код выполнялся при запуске файла с помощью:
+
+
+
+```console
+$ python myapp.py
+```
+
+
+
+но не вызывался, когда другой файл импортирует это, например::
+
+```Python
+from myapp import app
+```
+
+#### Больше деталей
+
+Давайте назовём ваш файл `myapp.py`.
+
+Если вы запустите его с помощью:
+
+
+
+```console
+$ python myapp.py
+```
+
+
+
+то встроенная переменная `__name__`, автоматически создаваемая Python в вашем файле, будет иметь значение строкового типа `"__main__"`.
+
+Тогда выполнится условие и эта часть кода:
+
+```Python
+ uvicorn.run(app, host="0.0.0.0", port=8000)
+```
+
+будет запущена.
+
+---
+
+Но этого не произойдет, если вы импортируете этот модуль (файл).
+
+Таким образом, если у вас есть файл `importer.py` с таким импортом:
+
+```Python
+from myapp import app
+
+# Some more code
+```
+
+то автоматическая создаваемая внутри файла `myapp.py` переменная `__name__` будет иметь значение отличающееся от `"__main__"`.
+
+Следовательно, строка:
+
+```Python
+ uvicorn.run(app, host="0.0.0.0", port=8000)
+```
+
+не будет выполнена.
+
+!!! Информация
+ Для получения дополнительной информации, ознакомьтесь с
официальной документацией Python.
+
+## Запуск вашего кода с помощью отладчика
+
+Так как вы запускаете сервер Uvicorn непосредственно из вашего кода, вы можете вызвать Python программу (ваше FastAPI приложение) напрямую из отладчика.
+
+---
+
+Например, в Visual Studio Code вы можете выполнить следующие шаги:
+
+* Перейдите на панель "Debug".
+* Выберите "Add configuration...".
+* Выберите "Python"
+* Запустите отладчик "`Python: Current File (Integrated Terminal)`".
+
+Это запустит сервер с вашим **FastAPI** кодом, остановится на точках останова, и т.д.
+
+Вот как это может выглядеть:
+
+

+
+---
+
+Если используете Pycharm, вы можете выполнить следующие шаги:
+
+* Открыть "Run" меню.
+* Выбрать опцию "Debug...".
+* Затем в появившемся контекстном меню.
+* Выбрать файл для отладки (в данном случае, `main.py`).
+
+Это запустит сервер с вашим **FastAPI** кодом, остановится на точках останова, и т.д.
+
+Вот как это может выглядеть:
+
+

diff --git a/docs/ru/docs/tutorial/first-steps.md b/docs/ru/docs/tutorial/first-steps.md
new file mode 100644
index 000000000..b46f235bc
--- /dev/null
+++ b/docs/ru/docs/tutorial/first-steps.md
@@ -0,0 +1,333 @@
+# Первые шаги
+
+Самый простой FastAPI файл может выглядеть так:
+
+```Python
+{!../../../docs_src/first_steps/tutorial001.py!}
+```
+
+Скопируйте в файл `main.py`.
+
+Запустите сервер в режиме реального времени:
+
+
+
+```console
+$ uvicorn main:app --reload
+
+INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
+INFO: Started reloader process [28720]
+INFO: Started server process [28722]
+INFO: Waiting for application startup.
+INFO: Application startup complete.
+```
+
+
+
+!!! note "Технические детали"
+ Команда `uvicorn main:app` обращается к:
+
+ * `main`: файл `main.py` (модуль Python).
+ * `app`: объект, созданный внутри файла `main.py` в строке `app = FastAPI()`.
+ * `--reload`: перезапускает сервер после изменения кода. Используйте только для разработки.
+
+В окне вывода появится следующая строка:
+
+```hl_lines="4"
+INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
+```
+
+Эта строка показывает URL-адрес, по которому приложение доступно на локальной машине.
+
+### Проверьте
+
+Откройте браузер по адресу:
http://127.0.0.1:8000.
+
+Вы увидите JSON-ответ следующего вида:
+
+```JSON
+{"message": "Hello World"}
+```
+
+### Интерактивная документация API
+
+Перейдите по адресу:
http://127.0.0.1:8000/docs.
+
+Вы увидите автоматически сгенерированную, интерактивную документацию по API (предоставленную
Swagger UI):
+
+
+
+### Альтернативная документация API
+
+Теперь перейдите по адресу
http://127.0.0.1:8000/redoc.
+
+Вы увидите альтернативную автоматически сгенерированную документацию (предоставленную
ReDoc):
+
+
+
+### OpenAPI
+
+**FastAPI** генерирует "схему" всего API, используя стандарт **OpenAPI**.
+
+#### "Схема"
+
+"Схема" - это определение или описание чего-либо. Не код, реализующий это, а только абстрактное описание.
+
+#### API "схема"
+
+
OpenAPI - это спецификация, которая определяет, как описывать схему API.
+
+Определение схемы содержит пути (paths) API, их параметры и т.п.
+
+#### "Схема" данных
+
+Термин "схема" также может относиться к формату или структуре некоторых данных, например, JSON.
+
+Тогда, подразумеваются атрибуты JSON, их типы данных и т.п.
+
+#### OpenAPI и JSON Schema
+
+OpenAPI описывает схему API. Эта схема содержит определения (или "схемы") данных, отправляемых и получаемых API. Для описания структуры данных в JSON используется стандарт **JSON Schema**.
+
+#### Рассмотрим `openapi.json`
+
+Если Вас интересует, как выглядит исходная схема OpenAPI, то FastAPI автоматически генерирует JSON-схему со всеми описаниями API.
+
+Можете посмотреть здесь:
http://127.0.0.1:8000/openapi.json.
+
+Вы увидите примерно такой JSON:
+
+```JSON
+{
+ "openapi": "3.0.2",
+ "info": {
+ "title": "FastAPI",
+ "version": "0.1.0"
+ },
+ "paths": {
+ "/items/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+
+
+
+...
+```
+
+#### Для чего нужен OpenAPI
+
+Схема OpenAPI является основой для обеих систем интерактивной документации.
+
+Существуют десятки альтернативных инструментов, основанных на OpenAPI. Вы можете легко добавить любой из них к **FastAPI** приложению.
+
+Вы также можете использовать OpenAPI для автоматической генерации кода для клиентов, которые взаимодействуют с API. Например, для фронтенд-, мобильных или IoT-приложений.
+
+## Рассмотрим поэтапно
+
+### Шаг 1: импортируйте `FastAPI`
+
+```Python hl_lines="1"
+{!../../../docs_src/first_steps/tutorial001.py!}
+```
+
+`FastAPI` это класс в Python, который предоставляет всю функциональность для API.
+
+!!! note "Технические детали"
+ `FastAPI` это класс, который наследуется непосредственно от `Starlette`.
+
+ Вы можете использовать всю функциональность
Starlette в `FastAPI`.
+
+### Шаг 2: создайте экземпляр `FastAPI`
+
+```Python hl_lines="3"
+{!../../../docs_src/first_steps/tutorial001.py!}
+```
+
+Переменная `app` является экземпляром класса `FastAPI`.
+
+Это единая точка входа для создания и взаимодействия с API.
+
+Именно к этой переменной `app` обращается `uvicorn` в команде:
+
+
+
+```console
+$ uvicorn main:app --reload
+
+INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
+```
+
+
+
+Если создать такое приложение:
+
+```Python hl_lines="3"
+{!../../../docs_src/first_steps/tutorial002.py!}
+```
+
+И поместить его в `main.py`, тогда вызов `uvicorn` будет таким:
+
+
+
+```console
+$ uvicorn main:my_awesome_api --reload
+
+INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
+```
+
+
+
+### Шаг 3: определите *операцию пути (path operation)*
+
+#### Путь (path)
+
+"Путь" это часть URL, после первого символа `/`, следующего за именем домена.
+
+Для URL:
+
+```
+https://example.com/items/foo
+```
+
+...путь выглядит так:
+
+```
+/items/foo
+```
+
+!!! info "Дополнительная иформация"
+ Термин "path" также часто называется "endpoint" или "route".
+
+При создании API, "путь" является основным способом разделения "задач" и "ресурсов".
+
+#### Операция (operation)
+
+"Операция" это один из "методов" HTTP.
+
+Таких, как:
+
+* `POST`
+* `GET`
+* `PUT`
+* `DELETE`
+
+...и более экзотических:
+
+* `OPTIONS`
+* `HEAD`
+* `PATCH`
+* `TRACE`
+
+По протоколу HTTP можно обращаться к каждому пути, используя один (или несколько) из этих "методов".
+
+---
+
+При создании API принято использовать конкретные HTTP-методы для выполнения определенных действий.
+
+Обычно используют:
+
+* `POST`: создать данные.
+* `GET`: прочитать.
+* `PUT`: изменить (обновить).
+* `DELETE`: удалить.
+
+В OpenAPI каждый HTTP метод называется "**операция**".
+
+Мы также будем придерживаться этого термина.
+
+#### Определите *декоратор операции пути (path operation decorator)*
+
+```Python hl_lines="6"
+{!../../../docs_src/first_steps/tutorial001.py!}
+```
+
+Декоратор `@app.get("/")` указывает **FastAPI**, что функция, прямо под ним, отвечает за обработку запросов, поступающих по адресу:
+
+* путь `/`
+* использующих
get
операцию
+
+!!! info "`@decorator` Дополнительная информация"
+ Синтаксис `@something` в Python называется "декоратор".
+
+ Вы помещаете его над функцией. Как красивую декоративную шляпу (думаю, что оттуда и происходит этот термин).
+
+ "Декоратор" принимает функцию ниже и выполняет с ней какое-то действие.
+
+ В нашем случае, этот декоратор сообщает **FastAPI**, что функция ниже соответствует **пути** `/` и **операции** `get`.
+
+ Это и есть "**декоратор операции пути**".
+
+Можно также использовать операции:
+
+* `@app.post()`
+* `@app.put()`
+* `@app.delete()`
+
+И более экзотические:
+
+* `@app.options()`
+* `@app.head()`
+* `@app.patch()`
+* `@app.trace()`
+
+!!! tip "Подсказка"
+ Вы можете использовать каждую операцию (HTTP-метод) по своему усмотрению.
+
+ **FastAPI** не навязывает определенного значения для каждого метода.
+
+ Информация здесь представлена как рекомендация, а не требование.
+
+ Например, при использовании GraphQL обычно все действия выполняются только с помощью POST операций.
+
+### Шаг 4: определите **функцию операции пути**
+
+Вот "**функция операции пути**":
+
+* **путь**: `/`.
+* **операция**: `get`.
+* **функция**: функция ниже "декоратора" (ниже `@app.get("/")`).
+
+```Python hl_lines="7"
+{!../../../docs_src/first_steps/tutorial001.py!}
+```
+
+Это обычная Python функция.
+
+**FastAPI** будет вызывать её каждый раз при получении `GET` запроса к URL "`/`".
+
+В данном случае это асинхронная функция.
+
+---
+
+Вы также можете определить ее как обычную функцию вместо `async def`:
+
+```Python hl_lines="7"
+{!../../../docs_src/first_steps/tutorial003.py!}
+```
+
+!!! note "Технические детали"
+ Если не знаете в чём разница, посмотрите [Конкурентность: *"Нет времени?"*](../async.md#in-a-hurry){.internal-link target=_blank}.
+
+### Шаг 5: верните результат
+
+```Python hl_lines="8"
+{!../../../docs_src/first_steps/tutorial001.py!}
+```
+
+Вы можете вернуть `dict`, `list`, отдельные значения `str`, `int` и т.д.
+
+Также можно вернуть модели Pydantic (рассмотрим это позже).
+
+Многие объекты и модели будут автоматически преобразованы в JSON (включая ORM). Пробуйте использовать другие объекты, которые предпочтительней для Вас, вероятно, они уже поддерживаются.
+
+## Резюме
+
+* Импортируем `FastAPI`.
+* Создаём экземпляр `app`.
+* Пишем **декоратор операции пути** (такой как `@app.get("/")`).
+* Пишем **функцию операции пути** (`def root(): ...`).
+* Запускаем сервер в режиме разработки (`uvicorn main:app --reload`).
diff --git a/docs/ru/docs/tutorial/index.md b/docs/ru/docs/tutorial/index.md
new file mode 100644
index 000000000..4277a6c4f
--- /dev/null
+++ b/docs/ru/docs/tutorial/index.md
@@ -0,0 +1,80 @@
+# Учебник - Руководство пользователя - Введение
+
+В этом руководстве шаг за шагом показано, как использовать **FastApi** с большинством его функций.
+
+Каждый раздел постепенно основывается на предыдущих, но он структурирован по отдельным темам, так что вы можете перейти непосредственно к конкретной теме для решения ваших конкретных потребностей в API.
+
+Он также создан для использования в качестве будущего справочника.
+
+Так что вы можете вернуться и посмотреть именно то, что вам нужно.
+
+## Запустите код
+
+Все блоки кода можно копировать и использовать напрямую (на самом деле это проверенные файлы Python).
+
+Чтобы запустить любой из примеров, скопируйте код в файл `main.py` и запустите `uvicorn` с параметрами:
+
+
+
+```console
+$ uvicorn main:app --reload
+
+INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
+INFO: Started reloader process [28720]
+INFO: Started server process [28722]
+INFO: Waiting for application startup.
+INFO: Application startup complete.
+```
+
+
+
+**НАСТОЯТЕЛЬНО рекомендуется**, чтобы вы написали или скопировали код, отредактировали его и запустили локально.
+
+Использование кода в вашем редакторе — это то, что действительно показывает вам преимущества FastAPI, видя, как мало кода вам нужно написать, все проверки типов, автодополнение и т.д.
+
+---
+
+## Установка FastAPI
+
+Первый шаг — установить FastAPI.
+
+Для руководства вы, возможно, захотите установить его со всеми дополнительными зависимостями и функциями:
+
+
+
+```console
+$ pip install "fastapi[all]"
+
+---> 100%
+```
+
+
+
+...это также включает `uvicorn`, который вы можете использовать в качестве сервера, который запускает ваш код.
+
+!!! note "Технические детали"
+ Вы также можете установить его по частям.
+
+ Это то, что вы, вероятно, сделаете, когда захотите развернуть свое приложение в рабочей среде:
+
+ ```
+ pip install fastapi
+ ```
+
+ Также установите `uvicorn` для работы в качестве сервера:
+
+ ```
+ pip install "uvicorn[standard]"
+ ```
+
+ И то же самое для каждой из необязательных зависимостей, которые вы хотите использовать.
+
+## Продвинутое руководство пользователя
+
+Существует также **Продвинутое руководство пользователя**, которое вы сможете прочитать после руководства **Учебник - Руководство пользователя**.
+
+**Продвинутое руководство пользователя** основано на этом, использует те же концепции и учит вас некоторым дополнительным функциям.
+
+Но вы должны сначала прочитать **Учебник - Руководство пользователя** (то, что вы читаете прямо сейчас).
+
+Он разработан таким образом, что вы можете создать полноценное приложение, используя только **Учебник - Руководство пользователя**, а затем расширить его различными способами, в зависимости от ваших потребностей, используя некоторые дополнительные идеи из **Продвинутого руководства пользователя**.
diff --git a/docs/ru/docs/tutorial/path-params-numeric-validations.md b/docs/ru/docs/tutorial/path-params-numeric-validations.md
new file mode 100644
index 000000000..0d034ef34
--- /dev/null
+++ b/docs/ru/docs/tutorial/path-params-numeric-validations.md
@@ -0,0 +1,292 @@
+# Path-параметры и валидация числовых данных
+
+Так же, как с помощью `Query` вы можете добавлять валидацию и метаданные для query-параметров, так и с помощью `Path` вы можете добавлять такую же валидацию и метаданные для path-параметров.
+
+## Импорт Path
+
+Сначала импортируйте `Path` из `fastapi`, а также импортируйте `Annotated`:
+
+=== "Python 3.10+"
+
+ ```Python hl_lines="1 3"
+ {!> ../../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py!}
+ ```
+
+=== "Python 3.9+"
+
+ ```Python hl_lines="1 3"
+ {!> ../../../docs_src/path_params_numeric_validations/tutorial001_an_py39.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="3-4"
+ {!> ../../../docs_src/path_params_numeric_validations/tutorial001_an.py!}
+ ```
+
+=== "Python 3.10+ без Annotated"
+
+ !!! tip "Подсказка"
+ Рекомендуется использовать версию с `Annotated` если возможно.
+
+ ```Python hl_lines="1"
+ {!> ../../../docs_src/path_params_numeric_validations/tutorial001_py310.py!}
+ ```
+
+=== "Python 3.6+ без Annotated"
+
+ !!! tip "Подсказка"
+ Рекомендуется использовать версию с `Annotated` если возможно.
+
+ ```Python hl_lines="3"
+ {!> ../../../docs_src/path_params_numeric_validations/tutorial001.py!}
+ ```
+
+!!! info "Информация"
+ Поддержка `Annotated` была добавлена в FastAPI начиная с версии 0.95.0 (и с этой версии рекомендуется использовать этот подход).
+
+ Если вы используете более старую версию, вы столкнётесь с ошибками при попытке использовать `Annotated`.
+
+ Убедитесь, что вы [обновили версию FastAPI](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} как минимум до 0.95.1 перед тем, как использовать `Annotated`.
+
+## Определите метаданные
+
+Вы можете указать все те же параметры, что и для `Query`.
+
+Например, чтобы указать значение метаданных `title` для path-параметра `item_id`, вы можете написать:
+
+=== "Python 3.10+"
+
+ ```Python hl_lines="10"
+ {!> ../../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py!}
+ ```
+
+=== "Python 3.9+"
+
+ ```Python hl_lines="10"
+ {!> ../../../docs_src/path_params_numeric_validations/tutorial001_an_py39.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="11"
+ {!> ../../../docs_src/path_params_numeric_validations/tutorial001_an.py!}
+ ```
+
+=== "Python 3.10+ без Annotated"
+
+ !!! tip "Подсказка"
+ Рекомендуется использовать версию с `Annotated` если возможно.
+
+ ```Python hl_lines="8"
+ {!> ../../../docs_src/path_params_numeric_validations/tutorial001_py310.py!}
+ ```
+
+=== "Python 3.6+ без Annotated"
+
+ !!! tip "Подсказка"
+ Рекомендуется использовать версию с `Annotated` если возможно.
+
+ ```Python hl_lines="10"
+ {!> ../../../docs_src/path_params_numeric_validations/tutorial001.py!}
+ ```
+
+!!! note "Примечание"
+ Path-параметр всегда является обязательным, поскольку он составляет часть пути.
+
+ Поэтому следует объявить его с помощью `...`, чтобы обозначить, что этот параметр обязательный.
+
+ Тем не менее, даже если вы объявите его как `None` или установите для него значение по умолчанию, это ни на что не повлияет и параметр останется обязательным.
+
+## Задайте нужный вам порядок параметров
+
+!!! tip "Подсказка"
+ Это не имеет большого значения, если вы используете `Annotated`.
+
+Допустим, вы хотите объявить query-параметр `q` как обязательный параметр типа `str`.
+
+И если вам больше ничего не нужно указывать для этого параметра, то нет необходимости использовать `Query`.
+
+Но вам по-прежнему нужно использовать `Path` для path-параметра `item_id`. И если по какой-либо причине вы не хотите использовать `Annotated`, то могут возникнуть небольшие сложности.
+
+Если вы поместите параметр со значением по умолчанию перед другим параметром, у которого нет значения по умолчанию, то Python укажет на ошибку.
+
+Но вы можете изменить порядок параметров, чтобы параметр без значения по умолчанию (query-параметр `q`) шёл первым.
+
+Это не имеет значения для **FastAPI**. Он распознает параметры по их названиям, типам и значениям по умолчанию (`Query`, `Path`, и т.д.), ему не важен их порядок.
+
+Поэтому вы можете определить функцию так:
+
+=== "Python 3.6 без Annotated"
+
+ !!! tip "Подсказка"
+ Рекомендуется использовать версию с `Annotated` если возможно.
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/path_params_numeric_validations/tutorial002.py!}
+ ```
+
+Но имейте в виду, что если вы используете `Annotated`, вы не столкнётесь с этой проблемой, так как вы не используете `Query()` или `Path()` в качестве значения по умолчанию для параметра функции.
+
+=== "Python 3.9+"
+
+ ```Python hl_lines="10"
+ {!> ../../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/path_params_numeric_validations/tutorial002_an.py!}
+ ```
+
+## Задайте нужный вам порядок параметров, полезные приёмы
+
+!!! tip "Подсказка"
+ Это не имеет большого значения, если вы используете `Annotated`.
+
+Здесь описан **небольшой приём**, который может оказаться удобным, хотя часто он вам не понадобится.
+
+Если вы хотите:
+
+* объявить query-параметр `q` без `Query` и без значения по умолчанию
+* объявить path-параметр `item_id` с помощью `Path`
+* указать их в другом порядке
+* не использовать `Annotated`
+
+...то вы можете использовать специальную возможность синтаксиса Python.
+
+Передайте `*` в качестве первого параметра функции.
+
+Python не будет ничего делать с `*`, но он будет знать, что все следующие параметры являются именованными аргументами (парами ключ-значение), также известными как
kwargs
, даже если у них нет значений по умолчанию.
+
+```Python hl_lines="7"
+{!../../../docs_src/path_params_numeric_validations/tutorial003.py!}
+```
+
+### Лучше с `Annotated`
+
+Имейте в виду, что если вы используете `Annotated`, то, поскольку вы не используете значений по умолчанию для параметров функции, то у вас не возникнет подобной проблемы и вам не придётся использовать `*`.
+
+=== "Python 3.9+"
+
+ ```Python hl_lines="10"
+ {!> ../../../docs_src/path_params_numeric_validations/tutorial003_an_py39.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/path_params_numeric_validations/tutorial003_an.py!}
+ ```
+
+## Валидация числовых данных: больше или равно
+
+С помощью `Query` и `Path` (и других классов, которые мы разберём позже) вы можете добавлять ограничения для числовых данных.
+
+В этом примере при указании `ge=1`, параметр `item_id` должен быть больше или равен `1` ("`g`reater than or `e`qual").
+
+=== "Python 3.9+"
+
+ ```Python hl_lines="10"
+ {!> ../../../docs_src/path_params_numeric_validations/tutorial004_an_py39.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/path_params_numeric_validations/tutorial004_an.py!}
+ ```
+
+=== "Python 3.6+ без Annotated"
+
+ !!! tip "Подсказка"
+ Рекомендуется использовать версию с `Annotated` если возможно.
+
+ ```Python hl_lines="8"
+ {!> ../../../docs_src/path_params_numeric_validations/tutorial004.py!}
+ ```
+
+## Валидация числовых данных: больше и меньше или равно
+
+То же самое применимо к:
+
+* `gt`: больше (`g`reater `t`han)
+* `le`: меньше или равно (`l`ess than or `e`qual)
+
+=== "Python 3.9+"
+
+ ```Python hl_lines="10"
+ {!> ../../../docs_src/path_params_numeric_validations/tutorial005_an_py39.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/path_params_numeric_validations/tutorial005_an.py!}
+ ```
+
+=== "Python 3.6+ без Annotated"
+
+ !!! tip "Подсказка"
+ Рекомендуется использовать версию с `Annotated` если возможно.
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/path_params_numeric_validations/tutorial005.py!}
+ ```
+
+## Валидация числовых данных: числа с плавающей точкой, больше и меньше
+
+Валидация также применима к значениям типа `float`.
+
+В этом случае становится важной возможность добавить ограничение
gt
, вместо
ge
, поскольку в таком случае вы можете, например, создать ограничение, чтобы значение было больше `0`, даже если оно меньше `1`.
+
+Таким образом, `0.5` будет корректным значением. А `0.0` или `0` — нет.
+
+То же самое справедливо и для
lt
.
+
+=== "Python 3.9+"
+
+ ```Python hl_lines="13"
+ {!> ../../../docs_src/path_params_numeric_validations/tutorial006_an_py39.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="12"
+ {!> ../../../docs_src/path_params_numeric_validations/tutorial006_an.py!}
+ ```
+
+=== "Python 3.6+ без Annotated"
+
+ !!! tip "Подсказка"
+ Рекомендуется использовать версию с `Annotated` если возможно.
+
+ ```Python hl_lines="11"
+ {!> ../../../docs_src/path_params_numeric_validations/tutorial006.py!}
+ ```
+
+## Резюме
+
+С помощью `Query`, `Path` (и других классов, которые мы пока не затронули) вы можете добавлять метаданные и строковую валидацию тем же способом, как и в главе [Query-параметры и валидация строк](query-params-str-validations.md){.internal-link target=_blank}.
+
+А также вы можете добавить валидацию числовых данных:
+
+* `gt`: больше (`g`reater `t`han)
+* `ge`: больше или равно (`g`reater than or `e`qual)
+* `lt`: меньше (`l`ess `t`han)
+* `le`: меньше или равно (`l`ess than or `e`qual)
+
+!!! info "Информация"
+ `Query`, `Path` и другие классы, которые мы разберём позже, являются наследниками общего класса `Param`.
+
+ Все они используют те же параметры для дополнительной валидации и метаданных, которые вы видели ранее.
+
+!!! note "Технические детали"
+ `Query`, `Path` и другие "классы", которые вы импортируете из `fastapi`, на самом деле являются функциями, которые при вызове возвращают экземпляры одноимённых классов.
+
+ Объект `Query`, который вы импортируете, является функцией. И при вызове она возвращает экземпляр одноимённого класса `Query`.
+
+ Использование функций (вместо использования классов напрямую) нужно для того, чтобы ваш редактор не подсвечивал ошибки, связанные с их типами.
+
+ Таким образом вы можете использовать привычный вам редактор и инструменты разработки, не добавляя дополнительных конфигураций для игнорирования подобных ошибок.
diff --git a/docs/ru/docs/tutorial/path-params.md b/docs/ru/docs/tutorial/path-params.md
new file mode 100644
index 000000000..55b498ef0
--- /dev/null
+++ b/docs/ru/docs/tutorial/path-params.md
@@ -0,0 +1,251 @@
+# Path-параметры
+
+Вы можете определить "параметры" или "переменные" пути, используя синтаксис форматированных строк Python:
+
+```Python hl_lines="6-7"
+{!../../../docs_src/path_params/tutorial001.py!}
+```
+
+Значение параметра пути `item_id` будет передано в функцию в качестве аргумента `item_id`.
+
+Если запустите этот пример и перейдёте по адресу:
http://127.0.0.1:8000/items/foo, то увидите ответ:
+
+```JSON
+{"item_id":"foo"}
+```
+
+## Параметры пути с типами
+
+Вы можете объявить тип параметра пути в функции, используя стандартные аннотации типов Python.
+
+```Python hl_lines="7"
+{!../../../docs_src/path_params/tutorial002.py!}
+```
+
+Здесь, `item_id` объявлен типом `int`.
+
+!!! check "Заметка"
+ Это обеспечит поддержку редактора внутри функции (проверка ошибок, автодополнение и т.п.).
+
+##
Преобразование данных
+
+Если запустите этот пример и перейдёте по адресу:
http://127.0.0.1:8000/items/3, то увидите ответ:
+
+```JSON
+{"item_id":3}
+```
+
+!!! check "Заметка"
+ Обратите внимание на значение `3`, которое получила (и вернула) функция. Это целочисленный Python `int`, а не строка `"3"`.
+
+ Используя определения типов, **FastAPI** выполняет автоматический
"парсинг" запросов.
+
+##
Проверка данных
+
+Если откроете браузер по адресу
http://127.0.0.1:8000/items/foo, то увидите интересную HTTP-ошибку:
+
+```JSON
+{
+ "detail": [
+ {
+ "loc": [
+ "path",
+ "item_id"
+ ],
+ "msg": "value is not a valid integer",
+ "type": "type_error.integer"
+ }
+ ]
+}
+```
+
+из-за того, что параметр пути `item_id` имеет значение `"foo"`, которое не является типом `int`.
+
+Та же ошибка возникнет, если вместо `int` передать `float` , например:
http://127.0.0.1:8000/items/4.2
+
+!!! check "Заметка"
+ **FastAPI** обеспечивает проверку типов, используя всё те же определения типов.
+
+ Обратите внимание, что в тексте ошибки явно указано место не прошедшее проверку.
+
+ Это очень полезно при разработке и отладке кода, который взаимодействует с API.
+
+## Документация
+
+И теперь, когда откроете браузер по адресу:
http://127.0.0.1:8000/docs, то увидите вот такую автоматически сгенерированную документацию API:
+
+

+
+!!! check "Заметка"
+ Ещё раз, просто используя определения типов, **FastAPI** обеспечивает автоматическую интерактивную документацию (с интеграцией Swagger UI).
+
+ Обратите внимание, что параметр пути объявлен целочисленным.
+
+## Преимущества стандартизации, альтернативная документация
+
+Поскольку сгенерированная схема соответствует стандарту
OpenAPI, её можно использовать со множеством совместимых инструментов.
+
+Именно поэтому, FastAPI сам предоставляет альтернативную документацию API (используя ReDoc), которую можно получить по адресу:
http://127.0.0.1:8000/redoc.
+
+

+
+По той же причине, есть множество совместимых инструментов, включая инструменты генерации кода для многих языков.
+
+## Pydantic
+
+Вся проверка данных выполняется под капотом с помощью
Pydantic. Поэтому вы можете быть уверены в качестве обработки данных.
+
+Вы можете использовать в аннотациях как простые типы данных, вроде `str`, `float`, `bool`, так и более сложные типы.
+
+Некоторые из них рассматриваются в следующих главах данного руководства.
+
+## Порядок имеет значение
+
+При создании *операций пути* можно столкнуться с ситуацией, когда путь является фиксированным.
+
+Например, `/users/me`. Предположим, что это путь для получения данных о текущем пользователе.
+
+У вас также может быть путь `/users/{user_id}`, чтобы получить данные о конкретном пользователе по его ID.
+
+Поскольку *операции пути* выполняются в порядке их объявления, необходимо, чтобы путь для `/users/me` был объявлен раньше, чем путь для `/users/{user_id}`:
+
+
+```Python hl_lines="6 11"
+{!../../../docs_src/path_params/tutorial003.py!}
+```
+
+Иначе путь для `/users/{user_id}` также будет соответствовать `/users/me`, "подразумевая", что он получает параметр `user_id` со значением `"me"`.
+
+Аналогично, вы не можете переопределить операцию с путем:
+
+```Python hl_lines="6 11"
+{!../../../docs_src/path_params/tutorial003b.py!}
+```
+
+Первый будет выполняться всегда, так как путь совпадает первым.
+
+## Предопределенные значения
+
+Что если нам нужно заранее определить допустимые *параметры пути*, которые *операция пути* может принимать? В таком случае можно использовать стандартное перечисление
`Enum` Python.
+
+### Создание класса `Enum`
+
+Импортируйте `Enum` и создайте подкласс, который наследуется от `str` и `Enum`.
+
+Мы наследуемся от `str`, чтобы документация API могла понять, что значения должны быть типа `string` и отображалась правильно.
+
+Затем создайте атрибуты класса с фиксированными допустимыми значениями:
+
+```Python hl_lines="1 6-9"
+{!../../../docs_src/path_params/tutorial005.py!}
+```
+
+!!! info "Дополнительная информация"
+
Перечисления (enum) доступны в Python начиная с версии 3.4.
+
+!!! tip "Подсказка"
+ Если интересно, то "AlexNet", "ResNet" и "LeNet" - это названия
моделей машинного обучения.
+
+### Определение *параметра пути*
+
+Определите *параметр пути*, используя в аннотации типа класс перечисления (`ModelName`), созданный ранее:
+
+```Python hl_lines="16"
+{!../../../docs_src/path_params/tutorial005.py!}
+```
+
+### Проверьте документацию
+
+Поскольку доступные значения *параметра пути* определены заранее, интерактивная документация может наглядно их отображать:
+
+

+
+### Работа с *перечислениями* в Python
+
+Значение *параметра пути* будет *элементом перечисления*.
+
+#### Сравнение *элементов перечисления*
+
+Вы можете сравнить это значение с *элементом перечисления* класса `ModelName`:
+
+```Python hl_lines="17"
+{!../../../docs_src/path_params/tutorial005.py!}
+```
+
+#### Получение *значения перечисления*
+
+Можно получить фактическое значение (в данном случае - `str`) с помощью `model_name.value` или в общем случае `your_enum_member.value`:
+
+```Python hl_lines="20"
+{!../../../docs_src/path_params/tutorial005.py!}
+```
+
+!!! tip "Подсказка"
+ Значение `"lenet"` также можно получить с помощью `ModelName.lenet.value`.
+
+#### Возврат *элементов перечисления*
+
+Из *операции пути* можно вернуть *элементы перечисления*, даже вложенные в тело JSON (например в `dict`).
+
+Они будут преобразованы в соответствующие значения (в данном случае - строки) перед их возвратом клиенту:
+
+```Python hl_lines="18 21 23"
+{!../../../docs_src/path_params/tutorial005.py!}
+```
+Вы отправите клиенту такой JSON-ответ:
+
+```JSON
+{
+ "model_name": "alexnet",
+ "message": "Deep Learning FTW!"
+}
+```
+
+## Path-параметры, содержащие пути
+
+Предположим, что есть *операция пути* с путем `/files/{file_path}`.
+
+Но вам нужно, чтобы `file_path` сам содержал *путь*, например, `home/johndoe/myfile.txt`.
+
+Тогда URL для этого файла будет такой: `/files/home/johndoe/myfile.txt`.
+
+### Поддержка OpenAPI
+
+OpenAPI не поддерживает способов объявления *параметра пути*, содержащего внутри *путь*, так как это может привести к сценариям, которые сложно определять и тестировать.
+
+Тем не менее это можно сделать в **FastAPI**, используя один из внутренних инструментов Starlette.
+
+Документация по-прежнему будет работать, хотя и не добавит никакой информации о том, что параметр должен содержать путь.
+
+### Конвертер пути
+
+Благодаря одной из опций Starlette, можете объявить *параметр пути*, содержащий *путь*, используя URL вроде:
+
+```
+/files/{file_path:path}
+```
+
+В этом случае `file_path` - это имя параметра, а часть `:path`, указывает, что параметр должен соответствовать любому *пути*.
+
+Можете использовать так:
+
+```Python hl_lines="6"
+{!../../../docs_src/path_params/tutorial004.py!}
+```
+
+!!! tip "Подсказка"
+ Возможно, вам понадобится, чтобы параметр содержал `/home/johndoe/myfile.txt` с ведущим слэшем (`/`).
+
+ В этом случае URL будет таким: `/files//home/johndoe/myfile.txt`, с двойным слэшем (`//`) между `files` и `home`.
+
+## Резюме
+Используя **FastAPI** вместе со стандартными объявлениями типов Python (короткими и интуитивно понятными), вы получаете:
+
+* Поддержку редактора (проверку ошибок, автозаполнение и т.п.)
+* "
Парсинг" данных
+* Валидацию данных
+* Автоматическую документацию API с указанием типов параметров.
+
+И объявлять типы достаточно один раз.
+
+Это, вероятно, является главным заметным преимуществом **FastAPI** по сравнению с альтернативными фреймворками (кроме
сырой производительности).
diff --git a/docs/ru/docs/tutorial/query-params.md b/docs/ru/docs/tutorial/query-params.md
new file mode 100644
index 000000000..68333ec56
--- /dev/null
+++ b/docs/ru/docs/tutorial/query-params.md
@@ -0,0 +1,225 @@
+# Query-параметры
+
+Когда вы объявляете параметры функции, которые не являются параметрами пути, они автоматически интерпретируются как "query"-параметры.
+
+```Python hl_lines="9"
+{!../../../docs_src/query_params/tutorial001.py!}
+```
+
+Query-параметры представляют из себя набор пар ключ-значение, которые идут после знака `?` в URL-адресе, разделенные символами `&`.
+
+Например, в этом URL-адресе:
+
+```
+http://127.0.0.1:8000/items/?skip=0&limit=10
+```
+
+...параметры запроса такие:
+
+* `skip`: со значением `0`
+* `limit`: со значением `10`
+
+Будучи частью URL-адреса, они "по умолчанию" являются строками.
+
+Но когда вы объявляете их с использованием аннотаций (в примере выше, как `int`), они конвертируются в указанный тип данных и проходят проверку на соответствие ему.
+
+Все те же правила, которые применяются к path-параметрам, также применяются и query-параметрам:
+
+* Поддержка от редактора кода (очевидно)
+*
"Парсинг" данных
+* Проверка на соответствие данных (Валидация)
+* Автоматическая документация
+
+## Значения по умолчанию
+
+Поскольку query-параметры не являются фиксированной частью пути, они могут быть не обязательными и иметь значения по умолчанию.
+
+В примере выше значения по умолчанию равны `skip=0` и `limit=10`.
+
+Таким образом, результат перехода по URL-адресу:
+
+```
+http://127.0.0.1:8000/items/
+```
+
+будет таким же, как если перейти используя параметры по умолчанию:
+
+```
+http://127.0.0.1:8000/items/?skip=0&limit=10
+```
+
+Но если вы введёте, например:
+
+```
+http://127.0.0.1:8000/items/?skip=20
+```
+
+Значения параметров в вашей функции будут:
+
+* `skip=20`: потому что вы установили это в URL-адресе
+* `limit=10`: т.к это было значение по умолчанию
+
+## Необязательные параметры
+
+Аналогично, вы можете объявлять необязательные query-параметры, установив их значение по умолчанию, равное `None`:
+
+=== "Python 3.10+"
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/query_params/tutorial002_py310.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/query_params/tutorial002.py!}
+ ```
+
+В этом случае, параметр `q` будет не обязательным и будет иметь значение `None` по умолчанию.
+
+!!! Важно
+ Также обратите внимание, что **FastAPI** достаточно умён чтобы заметить, что параметр `item_id` является path-параметром, а `q` нет, поэтому, это параметр запроса.
+
+## Преобразование типа параметра запроса
+
+Вы также можете объявлять параметры с типом `bool`, которые будут преобразованы соответственно:
+
+=== "Python 3.10+"
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/query_params/tutorial003_py310.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/query_params/tutorial003.py!}
+ ```
+
+В этом случае, если вы сделаете запрос:
+
+```
+http://127.0.0.1:8000/items/foo?short=1
+```
+
+или
+
+```
+http://127.0.0.1:8000/items/foo?short=True
+```
+
+или
+
+```
+http://127.0.0.1:8000/items/foo?short=true
+```
+
+или
+
+```
+http://127.0.0.1:8000/items/foo?short=on
+```
+
+или
+
+```
+http://127.0.0.1:8000/items/foo?short=yes
+```
+
+или в любом другом варианте написания (в верхнем регистре, с заглавной буквой, и т.п), внутри вашей функции параметр `short` будет иметь значение `True` типа данных `bool` . В противном случае - `False`.
+
+
+## Смешивание query-параметров и path-параметров
+
+Вы можете объявлять несколько query-параметров и path-параметров одновременно,**FastAPI** сам разберётся, что чем является.
+
+И вы не обязаны объявлять их в каком-либо определенном порядке.
+
+Они будут обнаружены по именам:
+
+=== "Python 3.10+"
+
+ ```Python hl_lines="6 8"
+ {!> ../../../docs_src/query_params/tutorial004_py310.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="8 10"
+ {!> ../../../docs_src/query_params/tutorial004.py!}
+ ```
+
+## Обязательные query-параметры
+
+Когда вы объявляете значение по умолчанию для параметра, который не является path-параметром (в этом разделе, мы пока что познакомились только с path-параметрами), то это значение не является обязательным.
+
+Если вы не хотите задавать конкретное значение, но хотите сделать параметр необязательным, вы можете установить значение по умолчанию равным `None`.
+
+Но если вы хотите сделать query-параметр обязательным, вы можете просто не указывать значение по умолчанию:
+
+```Python hl_lines="6-7"
+{!../../../docs_src/query_params/tutorial005.py!}
+```
+
+Здесь параметр запроса `needy` является обязательным параметром с типом данных `str`.
+
+Если вы откроете в браузере URL-адрес, например:
+
+```
+http://127.0.0.1:8000/items/foo-item
+```
+
+...без добавления обязательного параметра `needy`, вы увидите подобного рода ошибку:
+
+```JSON
+{
+ "detail": [
+ {
+ "loc": [
+ "query",
+ "needy"
+ ],
+ "msg": "field required",
+ "type": "value_error.missing"
+ }
+ ]
+}
+```
+
+Поскольку `needy` является обязательным параметром, вам необходимо указать его в URL-адресе:
+
+```
+http://127.0.0.1:8000/items/foo-item?needy=sooooneedy
+```
+
+...это будет работать:
+
+```JSON
+{
+ "item_id": "foo-item",
+ "needy": "sooooneedy"
+}
+```
+
+Конечно, вы можете определить некоторые параметры как обязательные, некоторые - со значением по умполчанию, а некоторые - полностью необязательные:
+
+=== "Python 3.10+"
+
+ ```Python hl_lines="8"
+ {!> ../../../docs_src/query_params/tutorial006_py310.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="10"
+ {!> ../../../docs_src/query_params/tutorial006.py!}
+ ```
+
+В этом примере, у нас есть 3 параметра запроса:
+
+* `needy`, обязательный `str`.
+* `skip`, типа `int` и со значением по умолчанию `0`.
+* `limit`, необязательный `int`.
+
+!!! подсказка
+ Вы можете использовать класс `Enum` также, как ранее применяли его с [Path-параметрами](path-params.md#predefined-values){.internal-link target=_blank}.
diff --git a/docs/ru/docs/tutorial/schema-extra-example.md b/docs/ru/docs/tutorial/schema-extra-example.md
new file mode 100644
index 000000000..a0363b9ba
--- /dev/null
+++ b/docs/ru/docs/tutorial/schema-extra-example.md
@@ -0,0 +1,189 @@
+# Объявление примера запроса данных
+
+Вы можете объявлять примеры данных, которые ваше приложение может получать.
+
+Вот несколько способов, как это можно сделать.
+
+## Pydantic `schema_extra`
+
+Вы можете объявить ключ `example` для модели Pydantic, используя класс `Config` и переменную `schema_extra`, как описано в
Pydantic документации: Настройка схемы:
+
+=== "Python 3.10+"
+
+ ```Python hl_lines="13-21"
+ {!> ../../../docs_src/schema_extra_example/tutorial001_py310.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="15-23"
+ {!> ../../../docs_src/schema_extra_example/tutorial001.py!}
+ ```
+
+Эта дополнительная информация будет включена в **JSON Schema** выходных данных для этой модели, и она будет использоваться в документации к API.
+
+!!! tip Подсказка
+ Вы можете использовать тот же метод для расширения JSON-схемы и добавления своей собственной дополнительной информации.
+
+ Например, вы можете использовать это для добавления дополнительной информации для пользовательского интерфейса в вашем веб-приложении и т.д.
+
+## Дополнительные аргументы поля `Field`
+
+При использовании `Field()` с моделями Pydantic, вы также можете объявлять дополнительную информацию для **JSON Schema**, передавая любые другие произвольные аргументы в функцию.
+
+Вы можете использовать это, чтобы добавить аргумент `example` для каждого поля:
+
+=== "Python 3.10+"
+
+ ```Python hl_lines="2 8-11"
+ {!> ../../../docs_src/schema_extra_example/tutorial002_py310.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="4 10-13"
+ {!> ../../../docs_src/schema_extra_example/tutorial002.py!}
+ ```
+
+!!! warning Внимание
+ Имейте в виду, что эти дополнительные переданные аргументы не добавляют никакой валидации, только дополнительную информацию для документации.
+
+## Использование `example` и `examples` в OpenAPI
+
+При использовании любой из этих функций:
+
+* `Path()`
+* `Query()`
+* `Header()`
+* `Cookie()`
+* `Body()`
+* `Form()`
+* `File()`
+
+вы также можете добавить аргумент, содержащий `example` или группу `examples` с дополнительной информацией, которая будет добавлена в **OpenAPI**.
+
+### Параметр `Body` с аргументом `example`
+
+Здесь мы передаём аргумент `example`, как пример данных ожидаемых в параметре `Body()`:
+
+=== "Python 3.10+"
+
+ ```Python hl_lines="22-27"
+ {!> ../../../docs_src/schema_extra_example/tutorial003_an_py310.py!}
+ ```
+
+=== "Python 3.9+"
+
+ ```Python hl_lines="22-27"
+ {!> ../../../docs_src/schema_extra_example/tutorial003_an_py39.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="23-28"
+ {!> ../../../docs_src/schema_extra_example/tutorial003_an.py!}
+ ```
+
+=== "Python 3.10+ non-Annotated"
+
+ !!! tip Заметка
+ Рекомендуется использовать версию с `Annotated`, если это возможно.
+
+ ```Python hl_lines="18-23"
+ {!> ../../../docs_src/schema_extra_example/tutorial003_py310.py!}
+ ```
+
+=== "Python 3.6+ non-Annotated"
+
+ !!! tip Заметка
+ Рекомендуется использовать версию с `Annotated`, если это возможно.
+
+ ```Python hl_lines="20-25"
+ {!> ../../../docs_src/schema_extra_example/tutorial003.py!}
+ ```
+
+### Аргумент "example" в UI документации
+
+С любым из вышеуказанных методов это будет выглядеть так в `/docs`:
+
+

+
+### `Body` с аргументом `examples`
+
+В качестве альтернативы одному аргументу `example`, вы можете передавать `examples` используя тип данных `dict` с **несколькими примерами**, каждый из которых содержит дополнительную информацию, которая также будет добавлена в **OpenAPI**.
+
+Ключи `dict` указывают на каждый пример, а значения для каждого из них - на еще один тип `dict` с дополнительной информацией.
+
+Каждый конкретный пример типа `dict` в аргументе `examples` может содержать:
+
+* `summary`: Краткое описание для примера.
+* `description`: Полное описание, которое может содержать текст в формате Markdown.
+* `value`: Это конкретный пример, который отображается, например, в виде типа `dict`.
+* `externalValue`: альтернатива параметру `value`, URL-адрес, указывающий на пример. Хотя это может не поддерживаться таким же количеством инструментов разработки и тестирования API, как параметр `value`.
+
+=== "Python 3.10+"
+
+ ```Python hl_lines="23-49"
+ {!> ../../../docs_src/schema_extra_example/tutorial004_an_py310.py!}
+ ```
+
+=== "Python 3.9+"
+
+ ```Python hl_lines="23-49"
+ {!> ../../../docs_src/schema_extra_example/tutorial004_an_py39.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="24-50"
+ {!> ../../../docs_src/schema_extra_example/tutorial004_an.py!}
+ ```
+
+=== "Python 3.10+ non-Annotated"
+
+ !!! tip Заметка
+ Рекомендуется использовать версию с `Annotated`, если это возможно.
+
+ ```Python hl_lines="19-45"
+ {!> ../../../docs_src/schema_extra_example/tutorial004_py310.py!}
+ ```
+
+=== "Python 3.6+ non-Annotated"
+
+ !!! tip Заметка
+ Рекомендуется использовать версию с `Annotated`, если это возможно.
+
+ ```Python hl_lines="21-47"
+ {!> ../../../docs_src/schema_extra_example/tutorial004.py!}
+ ```
+
+### Аргумент "examples" в UI документации
+
+С аргументом `examples`, добавленным в `Body()`, страница документации `/docs` будет выглядеть так:
+
+

+
+## Технические Детали
+
+!!! warning Внимание
+ Эти технические детали относятся к стандартам **JSON Schema** и **OpenAPI**.
+
+ Если предложенные выше идеи уже работают для вас, возможно этого будет достаточно и эти детали вам не потребуются, можете спокойно их пропустить.
+
+Когда вы добавляете пример внутрь модели Pydantic, используя `schema_extra` или `Field(example="something")`, этот пример добавляется в **JSON Schema** для данной модели Pydantic.
+
+И эта **JSON Schema** модели Pydantic включается в **OpenAPI** вашего API, а затем используется в UI документации.
+
+Поля `example` как такового не существует в стандартах **JSON Schema**. В последних версиях JSON-схемы определено поле
`examples`, но OpenAPI 3.0.3 основан на более старой версии JSON-схемы, которая не имела поля `examples`.
+
+Таким образом, OpenAPI 3.0.3 определяет своё собственное поле
`example` для модифицированной версии **JSON Schema**, которую он использует чтобы достичь той же цели (однако это именно поле `example`, а не `examples`), и именно это используется API в UI документации (с интеграцией Swagger UI).
+
+Итак, хотя поле `example` не является частью JSON-схемы, оно является частью настраиваемой версии JSON-схемы в OpenAPI, и именно это поле будет использоваться в UI документации.
+
+Однако, когда вы используете поле `example` или `examples` с любой другой функцией (`Query()`, `Body()`, и т.д.), эти примеры не добавляются в JSON-схему, которая описывает эти данные (даже в собственную версию JSON-схемы OpenAPI), они добавляются непосредственно в объявление *операции пути* в OpenAPI (вне частей OpenAPI, которые используют JSON-схему).
+
+Для функций `Path()`, `Query()`, `Header()`, и `Cookie()`, аргументы `example` или `examples` добавляются в
определение OpenAPI, к объекту `Parameter Object` (в спецификации).
+
+И для функций `Body()`, `File()` и `Form()` аргументы `example` или `examples` аналогично добавляются в
определение OpenAPI, к объекту `Request Body Object`, в поле `content` в объекте `Media Type Object` (в спецификации).
+
+С другой стороны, существует более новая версия OpenAPI: **3.1.0**, недавно выпущенная. Она основана на последней версии JSON-схемы и большинство модификаций из OpenAPI JSON-схемы удалены в обмен на новые возможности из последней версии JSON-схемы, так что все эти мелкие отличия устранены. Тем не менее, Swagger UI в настоящее время не поддерживает OpenAPI 3.1.0, поэтому пока лучше продолжать использовать вышеупомянутые методы.
diff --git a/docs/ru/docs/tutorial/static-files.md b/docs/ru/docs/tutorial/static-files.md
new file mode 100644
index 000000000..ec09eb5a3
--- /dev/null
+++ b/docs/ru/docs/tutorial/static-files.md
@@ -0,0 +1,40 @@
+# Статические Файлы
+
+Вы можете предоставлять статические файлы автоматически из директории, используя `StaticFiles`.
+
+## Использование `StaticFiles`
+
+* Импортируйте `StaticFiles`.
+* "Примонтируйте" экземпляр `StaticFiles()` с указанием определенной директории.
+
+```Python hl_lines="2 6"
+{!../../../docs_src/static_files/tutorial001.py!}
+```
+
+!!! заметка "Технические детали"
+ Вы также можете использовать `from starlette.staticfiles import StaticFiles`.
+
+ **FastAPI** предоставляет `starlette.staticfiles` под псевдонимом `fastapi.staticfiles`, просто для вашего удобства, как разработчика. Но на самом деле это берётся напрямую из библиотеки Starlette.
+
+### Что такое "Монтирование"
+
+"Монтирование" означает добавление полноценного "независимого" приложения в определенную директорию, которое затем обрабатывает все подпути.
+
+Это отличается от использования `APIRouter`, так как примонтированное приложение является полностью независимым.
+OpenAPI и документация из вашего главного приложения не будет содержать ничего из примонтированного приложения, и т.д.
+
+Вы можете прочитать больше об этом в **Расширенном руководстве пользователя**.
+
+## Детали
+
+Первый параметр `"/static"` относится к подпути, по которому это "подприложение" будет "примонтировано". Таким образом, любой путь начинающийся со `"/static"` будет обработан этим приложением.
+
+Параметр `directory="static"` относится к имени директории, которая содержит ваши статические файлы.
+
+`name="static"` даёт имя маршруту, которое может быть использовано внутри **FastAPI**.
+
+Все эти параметры могут отличаться от "`static`", настройте их в соответствии с вашими нуждами и конкретными деталями вашего собственного приложения.
+
+## Больше информации
+
+Для получения дополнительной информации о деталях и настройках ознакомьтесь с
Документацией Starlette о статических файлах.
diff --git a/docs/ru/mkdocs.yml b/docs/ru/mkdocs.yml
index 93fae36ce..ecd3aead1 100644
--- a/docs/ru/mkdocs.yml
+++ b/docs/ru/mkdocs.yml
@@ -67,17 +67,28 @@ nav:
- fastapi-people.md
- python-types.md
- Учебник - руководство пользователя:
+ - tutorial/index.md
+ - tutorial/first-steps.md
+ - tutorial/path-params.md
- tutorial/query-params-str-validations.md
+ - tutorial/path-params-numeric-validations.md
- tutorial/body-fields.md
- tutorial/background-tasks.md
- tutorial/extra-data-types.md
- tutorial/cookie-params.md
- tutorial/testing.md
- tutorial/response-status-code.md
+ - tutorial/query-params.md
+ - tutorial/body-multiple-params.md
+ - tutorial/static-files.md
+ - tutorial/debugging.md
+ - tutorial/schema-extra-example.md
+ - tutorial/body-nested-models.md
- async.md
- Развёртывание:
- deployment/index.md
- deployment/versions.md
+ - deployment/concepts.md
- deployment/https.md
- deployment/manually.md
- project-generation.md
diff --git a/docs/zh/docs/advanced/response-change-status-code.md b/docs/zh/docs/advanced/response-change-status-code.md
new file mode 100644
index 000000000..a289cf201
--- /dev/null
+++ b/docs/zh/docs/advanced/response-change-status-code.md
@@ -0,0 +1,31 @@
+# 响应 - 更改状态码
+
+你可能之前已经了解到,你可以设置默认的[响应状态码](../tutorial/response-status-code.md){.internal-link target=_blank}。
+
+但在某些情况下,你需要返回一个不同于默认值的状态码。
+
+## 使用场景
+
+例如,假设你想默认返回一个HTTP状态码为“OK”`200`。
+
+但如果数据不存在,你想创建它,并返回一个HTTP状态码为“CREATED”`201`。
+
+但你仍然希望能够使用`response_model`过滤和转换你返回的数据。
+
+对于这些情况,你可以使用一个`Response`参数。
+
+## 使用 `Response` 参数
+
+你可以在你的*路径操作函数*中声明一个`Response`类型的参数(就像你可以为cookies和头部做的那样)。
+
+然后你可以在这个*临时*响应对象中设置`status_code`。
+
+```Python hl_lines="1 9 12"
+{!../../../docs_src/response_change_status_code/tutorial001.py!}
+```
+
+然后你可以像平常一样返回任何你需要的对象(例如一个`dict`或者一个数据库模型)。如果你声明了一个`response_model`,它仍然会被用来过滤和转换你返回的对象。
+
+**FastAPI**将使用这个临时响应来提取状态码(也包括cookies和头部),并将它们放入包含你返回的值的最终响应中,该响应由任何`response_model`过滤。
+
+你也可以在依赖项中声明`Response`参数,并在其中设置状态码。但请注意,最后设置的状态码将会生效。
diff --git a/docs/zh/docs/advanced/response-headers.md b/docs/zh/docs/advanced/response-headers.md
new file mode 100644
index 000000000..85dab15ac
--- /dev/null
+++ b/docs/zh/docs/advanced/response-headers.md
@@ -0,0 +1,39 @@
+# 响应头
+
+## 使用 `Response` 参数
+
+你可以在你的*路径操作函数*中声明一个`Response`类型的参数(就像你可以为cookies做的那样)。
+
+然后你可以在这个*临时*响应对象中设置头部。
+```Python hl_lines="1 7-8"
+{!../../../docs_src/response_headers/tutorial002.py!}
+```
+
+然后你可以像平常一样返回任何你需要的对象(例如一个`dict`或者一个数据库模型)。如果你声明了一个`response_model`,它仍然会被用来过滤和转换你返回的对象。
+
+**FastAPI**将使用这个临时响应来提取头部(也包括cookies和状态码),并将它们放入包含你返回的值的最终响应中,该响应由任何`response_model`过滤。
+
+你也可以在依赖项中声明`Response`参数,并在其中设置头部(和cookies)。
+
+## 直接返回 `Response`
+
+你也可以在直接返回`Response`时添加头部。
+
+按照[直接返回响应](response-directly.md){.internal-link target=_blank}中所述创建响应,并将头部作为附加参数传递:
+```Python hl_lines="10-12"
+{!../../../docs_src/response_headers/tutorial001.py!}
+```
+
+
+!!! 注意 "技术细节"
+ 你也可以使用`from starlette.responses import Response`或`from starlette.responses import JSONResponse`。
+
+ **FastAPI**提供了与`fastapi.responses`相同的`starlette.responses`,只是为了方便开发者。但是,大多数可用的响应都直接来自Starlette。
+
+ 由于`Response`经常用于设置头部和cookies,因此**FastAPI**还在`fastapi.Response`中提供了它。
+
+## 自定义头部
+
+请注意,可以使用'X-'前缀添加自定义专有头部。
+
+但是,如果你有自定义头部,你希望浏览器中的客户端能够看到它们,你需要将它们添加到你的CORS配置中(在[CORS(跨源资源共享)](../tutorial/cors.md){.internal-link target=_blank}中阅读更多),使用在
Starlette的CORS文档中记录的`expose_headers`参数。
diff --git a/docs/zh/docs/contributing.md b/docs/zh/docs/contributing.md
index 36c3631c4..4ebd67315 100644
--- a/docs/zh/docs/contributing.md
+++ b/docs/zh/docs/contributing.md
@@ -97,7 +97,7 @@ $ python -m venv env
```console
-$ pip install -e ."[dev,doc,test]"
+$ pip install -r requirements.txt
---> 100%
```
diff --git a/docs/zh/docs/tutorial/static-files.md b/docs/zh/docs/tutorial/static-files.md
new file mode 100644
index 000000000..e7c5c3f0a
--- /dev/null
+++ b/docs/zh/docs/tutorial/static-files.md
@@ -0,0 +1,39 @@
+# 静态文件
+
+您可以使用 `StaticFiles`从目录中自动提供静态文件。
+
+## 使用`StaticFiles`
+
+* 导入`StaticFiles`。
+* "挂载"(Mount) 一个 `StaticFiles()` 实例到一个指定路径。
+
+```Python hl_lines="2 6"
+{!../../../docs_src/static_files/tutorial001.py!}
+```
+
+!!! note "技术细节"
+ 你也可以用 `from starlette.staticfiles import StaticFiles`。
+
+ **FastAPI** 提供了和 `starlette.staticfiles` 相同的 `fastapi.staticfiles` ,只是为了方便你,开发者。但它确实来自Starlette。
+
+### 什么是"挂载"(Mounting)
+
+"挂载" 表示在特定路径添加一个完全"独立的"应用,然后负责处理所有子路径。
+
+这与使用`APIRouter`不同,因为安装的应用程序是完全独立的。OpenAPI和来自你主应用的文档不会包含已挂载应用的任何东西等等。
+
+你可以在**高级用户指南**中了解更多。
+
+## 细节
+
+这个 "子应用" 会被 "挂载" 到第一个 `"/static"` 指向的子路径。因此,任何以`"/static"`开头的路径都会被它处理。
+
+ `directory="static"` 指向包含你的静态文件的目录名字。
+
+`name="static"` 提供了一个能被**FastAPI**内部使用的名字。
+
+所有这些参数可以不同于"`static`",根据你应用的需要和具体细节调整它们。
+
+## 更多信息
+
+更多细节和选择查阅
Starlette's docs about Static Files.
diff --git a/docs/zh/mkdocs.yml b/docs/zh/mkdocs.yml
index ef7ab1fd9..522c83766 100644
--- a/docs/zh/mkdocs.yml
+++ b/docs/zh/mkdocs.yml
@@ -108,6 +108,7 @@ nav:
- tutorial/sql-databases.md
- tutorial/bigger-applications.md
- tutorial/metadata.md
+ - tutorial/static-files.md
- tutorial/debugging.md
- 高级用户指南:
- advanced/index.md
@@ -116,6 +117,8 @@ nav:
- advanced/response-directly.md
- advanced/custom-response.md
- advanced/response-cookies.md
+ - advanced/response-change-status-code.md
+ - advanced/response-headers.md
- advanced/wsgi.md
- contributing.md
- help-fastapi.md
diff --git a/docs_src/sql_databases/sql_app/tests/test_sql_app.py b/docs_src/sql_databases/sql_app/tests/test_sql_app.py
index c60c3356f..5f55add0a 100644
--- a/docs_src/sql_databases/sql_app/tests/test_sql_app.py
+++ b/docs_src/sql_databases/sql_app/tests/test_sql_app.py
@@ -1,14 +1,17 @@
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
+from sqlalchemy.pool import StaticPool
from ..database import Base
from ..main import app, get_db
-SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
+SQLALCHEMY_DATABASE_URL = "sqlite://"
engine = create_engine(
- SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
+ SQLALCHEMY_DATABASE_URL,
+ connect_args={"check_same_thread": False},
+ poolclass=StaticPool,
)
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
diff --git a/docs_src/wsgi/tutorial001.py b/docs_src/wsgi/tutorial001.py
index 500ecf883..7f27a85a1 100644
--- a/docs_src/wsgi/tutorial001.py
+++ b/docs_src/wsgi/tutorial001.py
@@ -1,6 +1,7 @@
from fastapi import FastAPI
from fastapi.middleware.wsgi import WSGIMiddleware
-from flask import Flask, escape, request
+from flask import Flask, request
+from markupsafe import escape
flask_app = Flask(__name__)
diff --git a/fastapi/__init__.py b/fastapi/__init__.py
index e1c2be990..46a056363 100644
--- a/fastapi/__init__.py
+++ b/fastapi/__init__.py
@@ -1,6 +1,6 @@
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
-__version__ = "0.95.1"
+__version__ = "0.97.0"
from starlette import status as status
diff --git a/fastapi/applications.py b/fastapi/applications.py
index 8b3a74d3c..9b161c5ec 100644
--- a/fastapi/applications.py
+++ b/fastapi/applications.py
@@ -19,8 +19,9 @@ from fastapi.encoders import DictIntStrAny, SetIntStr
from fastapi.exception_handlers import (
http_exception_handler,
request_validation_exception_handler,
+ websocket_request_validation_exception_handler,
)
-from fastapi.exceptions import RequestValidationError
+from fastapi.exceptions import RequestValidationError, WebSocketRequestValidationError
from fastapi.logger import logger
from fastapi.middleware.asyncexitstack import AsyncExitStackMiddleware
from fastapi.openapi.docs import (
@@ -61,6 +62,7 @@ class FastAPI(Starlette):
servers: Optional[List[Dict[str, Union[str, Any]]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
default_response_class: Type[Response] = Default(JSONResponse),
+ redirect_slashes: bool = True,
docs_url: Optional[str] = "/docs",
redoc_url: Optional[str] = "/redoc",
swagger_ui_oauth2_redirect_url: Optional[str] = "/docs/oauth2-redirect",
@@ -126,6 +128,7 @@ class FastAPI(Starlette):
self.dependency_overrides: Dict[Callable[..., Any], Callable[..., Any]] = {}
self.router: routing.APIRouter = routing.APIRouter(
routes=routes,
+ redirect_slashes=redirect_slashes,
dependency_overrides_provider=self,
on_startup=on_startup,
on_shutdown=on_shutdown,
@@ -145,6 +148,11 @@ class FastAPI(Starlette):
self.exception_handlers.setdefault(
RequestValidationError, request_validation_exception_handler
)
+ self.exception_handlers.setdefault(
+ WebSocketRequestValidationError,
+ # Starlette still has incorrect type specification for the handlers
+ websocket_request_validation_exception_handler, # type: ignore
+ )
self.user_middleware: List[Middleware] = (
[] if middleware is None else list(middleware)
@@ -395,15 +403,34 @@ class FastAPI(Starlette):
return decorator
def add_api_websocket_route(
- self, path: str, endpoint: Callable[..., Any], name: Optional[str] = None
+ self,
+ path: str,
+ endpoint: Callable[..., Any],
+ name: Optional[str] = None,
+ *,
+ dependencies: Optional[Sequence[Depends]] = None,
) -> None:
- self.router.add_api_websocket_route(path, endpoint, name=name)
+ self.router.add_api_websocket_route(
+ path,
+ endpoint,
+ name=name,
+ dependencies=dependencies,
+ )
def websocket(
- self, path: str, name: Optional[str] = None
+ self,
+ path: str,
+ name: Optional[str] = None,
+ *,
+ dependencies: Optional[Sequence[Depends]] = None,
) -> Callable[[DecoratedCallable], DecoratedCallable]:
def decorator(func: DecoratedCallable) -> DecoratedCallable:
- self.add_api_websocket_route(path, func, name=name)
+ self.add_api_websocket_route(
+ path,
+ func,
+ name=name,
+ dependencies=dependencies,
+ )
return func
return decorator
diff --git a/fastapi/exception_handlers.py b/fastapi/exception_handlers.py
index 4d7ea5ec2..6c2ba7fed 100644
--- a/fastapi/exception_handlers.py
+++ b/fastapi/exception_handlers.py
@@ -1,10 +1,11 @@
from fastapi.encoders import jsonable_encoder
-from fastapi.exceptions import RequestValidationError
+from fastapi.exceptions import RequestValidationError, WebSocketRequestValidationError
from fastapi.utils import is_body_allowed_for_status_code
+from fastapi.websockets import WebSocket
from starlette.exceptions import HTTPException
from starlette.requests import Request
from starlette.responses import JSONResponse, Response
-from starlette.status import HTTP_422_UNPROCESSABLE_ENTITY
+from starlette.status import HTTP_422_UNPROCESSABLE_ENTITY, WS_1008_POLICY_VIOLATION
async def http_exception_handler(request: Request, exc: HTTPException) -> Response:
@@ -23,3 +24,11 @@ async def request_validation_exception_handler(
status_code=HTTP_422_UNPROCESSABLE_ENTITY,
content={"detail": jsonable_encoder(exc.errors())},
)
+
+
+async def websocket_request_validation_exception_handler(
+ websocket: WebSocket, exc: WebSocketRequestValidationError
+) -> None:
+ await websocket.close(
+ code=WS_1008_POLICY_VIOLATION, reason=jsonable_encoder(exc.errors())
+ )
diff --git a/fastapi/exceptions.py b/fastapi/exceptions.py
index 49726fa92..57fb24df3 100644
--- a/fastapi/exceptions.py
+++ b/fastapi/exceptions.py
@@ -11,7 +11,7 @@ class HTTPException(StarletteHTTPException):
self,
status_code: int,
detail: Any = None,
- headers: Optional[Dict[str, Any]] = None,
+ headers: Optional[Dict[str, str]] = None,
) -> None:
super().__init__(status_code=status_code, detail=detail, headers=headers)
diff --git a/fastapi/middleware/asyncexitstack.py b/fastapi/middleware/asyncexitstack.py
index 503a68ac7..30a0ae626 100644
--- a/fastapi/middleware/asyncexitstack.py
+++ b/fastapi/middleware/asyncexitstack.py
@@ -10,19 +10,16 @@ class AsyncExitStackMiddleware:
self.context_name = context_name
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
- if AsyncExitStack:
- dependency_exception: Optional[Exception] = None
- async with AsyncExitStack() as stack:
- scope[self.context_name] = stack
- try:
- await self.app(scope, receive, send)
- except Exception as e:
- dependency_exception = e
- raise e
- if dependency_exception:
- # This exception was possibly handled by the dependency but it should
- # still bubble up so that the ServerErrorMiddleware can return a 500
- # or the ExceptionMiddleware can catch and handle any other exceptions
- raise dependency_exception
- else:
- await self.app(scope, receive, send) # pragma: no cover
+ dependency_exception: Optional[Exception] = None
+ async with AsyncExitStack() as stack:
+ scope[self.context_name] = stack
+ try:
+ await self.app(scope, receive, send)
+ except Exception as e:
+ dependency_exception = e
+ raise e
+ if dependency_exception:
+ # This exception was possibly handled by the dependency but it should
+ # still bubble up so that the ServerErrorMiddleware can return a 500
+ # or the ExceptionMiddleware can catch and handle any other exceptions
+ raise dependency_exception
diff --git a/fastapi/openapi/models.py b/fastapi/openapi/models.py
index 35aa1672b..81a24f389 100644
--- a/fastapi/openapi/models.py
+++ b/fastapi/openapi/models.py
@@ -3,6 +3,7 @@ from typing import Any, Callable, Dict, Iterable, List, Optional, Union
from fastapi.logger import logger
from pydantic import AnyUrl, BaseModel, Field
+from typing_extensions import Literal
try:
import email_validator # type: ignore
@@ -108,14 +109,14 @@ class Schema(BaseModel):
exclusiveMaximum: Optional[float] = None
minimum: Optional[float] = None
exclusiveMinimum: Optional[float] = None
- maxLength: Optional[int] = Field(default=None, gte=0)
- minLength: Optional[int] = Field(default=None, gte=0)
+ maxLength: Optional[int] = Field(default=None, ge=0)
+ minLength: Optional[int] = Field(default=None, ge=0)
pattern: Optional[str] = None
- maxItems: Optional[int] = Field(default=None, gte=0)
- minItems: Optional[int] = Field(default=None, gte=0)
+ maxItems: Optional[int] = Field(default=None, ge=0)
+ minItems: Optional[int] = Field(default=None, ge=0)
uniqueItems: Optional[bool] = None
- maxProperties: Optional[int] = Field(default=None, gte=0)
- minProperties: Optional[int] = Field(default=None, gte=0)
+ maxProperties: Optional[int] = Field(default=None, ge=0)
+ minProperties: Optional[int] = Field(default=None, ge=0)
required: Optional[List[str]] = None
enum: Optional[List[Any]] = None
type: Optional[str] = None
@@ -298,18 +299,18 @@ class APIKeyIn(Enum):
class APIKey(SecurityBase):
- type_ = Field(SecuritySchemeType.apiKey, alias="type")
+ type_: SecuritySchemeType = Field(default=SecuritySchemeType.apiKey, alias="type")
in_: APIKeyIn = Field(alias="in")
name: str
class HTTPBase(SecurityBase):
- type_ = Field(SecuritySchemeType.http, alias="type")
+ type_: SecuritySchemeType = Field(default=SecuritySchemeType.http, alias="type")
scheme: str
class HTTPBearer(HTTPBase):
- scheme = "bearer"
+ scheme: Literal["bearer"] = "bearer"
bearerFormat: Optional[str] = None
@@ -349,12 +350,14 @@ class OAuthFlows(BaseModel):
class OAuth2(SecurityBase):
- type_ = Field(SecuritySchemeType.oauth2, alias="type")
+ type_: SecuritySchemeType = Field(default=SecuritySchemeType.oauth2, alias="type")
flows: OAuthFlows
class OpenIdConnect(SecurityBase):
- type_ = Field(SecuritySchemeType.openIdConnect, alias="type")
+ type_: SecuritySchemeType = Field(
+ default=SecuritySchemeType.openIdConnect, alias="type"
+ )
openIdConnectUrl: str
diff --git a/fastapi/openapi/utils.py b/fastapi/openapi/utils.py
index 86e15b46d..6d736647b 100644
--- a/fastapi/openapi/utils.py
+++ b/fastapi/openapi/utils.py
@@ -181,7 +181,7 @@ def get_openapi_operation_metadata(
file_name = getattr(route.endpoint, "__globals__", {}).get("__file__")
if file_name:
message += f" at {file_name}"
- warnings.warn(message)
+ warnings.warn(message, stacklevel=1)
operation_ids.add(operation_id)
operation["operationId"] = operation_id
if route.deprecated:
@@ -332,10 +332,8 @@ def get_openapi_path(
openapi_response["description"] = description
http422 = str(HTTP_422_UNPROCESSABLE_ENTITY)
if (all_route_params or route.body_field) and not any(
- [
- status in operation["responses"]
- for status in [http422, "4XX", "default"]
- ]
+ status in operation["responses"]
+ for status in [http422, "4XX", "default"]
):
operation["responses"][http422] = {
"description": "Validation Error",
diff --git a/fastapi/responses.py b/fastapi/responses.py
index 88dba96e8..c0a13b755 100644
--- a/fastapi/responses.py
+++ b/fastapi/responses.py
@@ -27,8 +27,6 @@ class UJSONResponse(JSONResponse):
class ORJSONResponse(JSONResponse):
- media_type = "application/json"
-
def render(self, content: Any) -> bytes:
assert orjson is not None, "orjson must be installed to use ORJSONResponse"
return orjson.dumps(
diff --git a/fastapi/routing.py b/fastapi/routing.py
index ed3279cb2..def9d1c0f 100644
--- a/fastapi/routing.py
+++ b/fastapi/routing.py
@@ -32,6 +32,7 @@ from fastapi.dependencies.utils import (
)
from fastapi.encoders import DictIntStrAny, SetIntStr, jsonable_encoder
from fastapi.exceptions import (
+ FastAPIError,
RequestValidationError,
RouteAlreadyExistsError,
WebSocketRequestValidationError,
@@ -53,15 +54,15 @@ from starlette.concurrency import run_in_threadpool
from starlette.exceptions import HTTPException
from starlette.requests import Request
from starlette.responses import JSONResponse, Response
-from starlette.routing import BaseRoute, Match
-from starlette.routing import Mount as Mount # noqa
from starlette.routing import (
+ BaseRoute,
+ Match,
compile_path,
get_name,
request_response,
websocket_session,
)
-from starlette.status import WS_1008_POLICY_VIOLATION
+from starlette.routing import Mount as Mount # noqa
from starlette.types import ASGIApp, Lifespan, Scope
from starlette.websockets import WebSocket
@@ -288,7 +289,6 @@ def get_websocket_app(
)
values, errors, _, _2, _3 = solved_result
if errors:
- await websocket.close(code=WS_1008_POLICY_VIOLATION)
raise WebSocketRequestValidationError(errors)
assert dependant.call is not None, "dependant.call must be a function"
await dependant.call(**values)
@@ -303,13 +303,21 @@ class APIWebSocketRoute(routing.WebSocketRoute):
endpoint: Callable[..., Any],
*,
name: Optional[str] = None,
+ dependencies: Optional[Sequence[params.Depends]] = None,
dependency_overrides_provider: Optional[Any] = None,
) -> None:
self.path = path
self.endpoint = endpoint
self.name = get_name(endpoint) if name is None else name
+ self.dependencies = list(dependencies or [])
self.path_regex, self.path_format, self.param_convertors = compile_path(path)
self.dependant = get_dependant(path=self.path_format, call=self.endpoint)
+ for depends in self.dependencies[::-1]:
+ self.dependant.dependencies.insert(
+ 0,
+ get_parameterless_sub_dependant(depends=depends, path=self.path_format),
+ )
+
self.app = websocket_session(
get_websocket_app(
dependant=self.dependant,
@@ -423,10 +431,7 @@ class APIRoute(routing.Route):
else:
self.response_field = None # type: ignore
self.secure_cloned_response_field = None
- if dependencies:
- self.dependencies = list(dependencies)
- else:
- self.dependencies = []
+ self.dependencies = list(dependencies or [])
self.description = description or inspect.cleandoc(self.endpoint.__doc__ or "")
# if a "form feed" character (page break) is found in the description text,
# truncate description text to the content preceding the first "form feed"
@@ -522,7 +527,7 @@ class APIRouter(routing.Router):
), "A path prefix must not end with '/', as the routes will start with '/'"
self.prefix = prefix
self.tags: List[Union[str, Enum]] = tags or []
- self.dependencies = list(dependencies or []) or []
+ self.dependencies = list(dependencies or [])
self.deprecated = deprecated
self.include_in_schema = include_in_schema
self.responses = responses or {}
@@ -700,12 +705,22 @@ class APIRouter(routing.Router):
return decorator
def add_api_websocket_route(
- self, path: str, endpoint: Callable[..., Any], name: Optional[str] = None
+ self,
+ path: str,
+ endpoint: Callable[..., Any],
+ name: Optional[str] = None,
+ *,
+ dependencies: Optional[Sequence[params.Depends]] = None,
) -> None:
+ current_dependencies = self.dependencies.copy()
+ if dependencies:
+ current_dependencies.extend(dependencies)
+
route = APIWebSocketRoute(
self.prefix + path,
endpoint=endpoint,
name=name,
+ dependencies=current_dependencies,
dependency_overrides_provider=self.dependency_overrides_provider,
)
hash_val = get_path_hash_val(route.path)
@@ -715,10 +730,16 @@ class APIRouter(routing.Router):
self.routes.append(route)
def websocket(
- self, path: str, name: Optional[str] = None
+ self,
+ path: str,
+ name: Optional[str] = None,
+ *,
+ dependencies: Optional[Sequence[params.Depends]] = None,
) -> Callable[[DecoratedCallable], DecoratedCallable]:
def decorator(func: DecoratedCallable) -> DecoratedCallable:
- self.add_api_websocket_route(path, func, name=name)
+ self.add_api_websocket_route(
+ path, func, name=name, dependencies=dependencies
+ )
return func
return decorator
@@ -758,7 +779,7 @@ class APIRouter(routing.Router):
path = getattr(r, "path") # noqa: B009
name = getattr(r, "name", "unknown")
if path is not None and not path:
- raise Exception(
+ raise FastAPIError(
f"Prefix and path cannot be both empty (path operation: {name})"
)
if responses is None:
@@ -837,8 +858,16 @@ class APIRouter(routing.Router):
name=route.name,
)
elif isinstance(route, APIWebSocketRoute):
+ current_dependencies = []
+ if dependencies:
+ current_dependencies.extend(dependencies)
+ if route.dependencies:
+ current_dependencies.extend(route.dependencies)
self.add_api_websocket_route(
- prefix + route.path, route.endpoint, name=route.name
+ prefix + route.path,
+ route.endpoint,
+ dependencies=current_dependencies,
+ name=route.name,
)
elif isinstance(route, routing.WebSocketRoute):
self.add_websocket_route(
diff --git a/fastapi/security/api_key.py b/fastapi/security/api_key.py
index 61730187a..8b2c5c080 100644
--- a/fastapi/security/api_key.py
+++ b/fastapi/security/api_key.py
@@ -21,7 +21,9 @@ class APIKeyQuery(APIKeyBase):
auto_error: bool = True,
):
self.model: APIKey = APIKey(
- **{"in": APIKeyIn.query}, name=name, description=description
+ **{"in": APIKeyIn.query}, # type: ignore[arg-type]
+ name=name,
+ description=description,
)
self.scheme_name = scheme_name or self.__class__.__name__
self.auto_error = auto_error
@@ -48,7 +50,9 @@ class APIKeyHeader(APIKeyBase):
auto_error: bool = True,
):
self.model: APIKey = APIKey(
- **{"in": APIKeyIn.header}, name=name, description=description
+ **{"in": APIKeyIn.header}, # type: ignore[arg-type]
+ name=name,
+ description=description,
)
self.scheme_name = scheme_name or self.__class__.__name__
self.auto_error = auto_error
@@ -75,7 +79,9 @@ class APIKeyCookie(APIKeyBase):
auto_error: bool = True,
):
self.model: APIKey = APIKey(
- **{"in": APIKeyIn.cookie}, name=name, description=description
+ **{"in": APIKeyIn.cookie}, # type: ignore[arg-type]
+ name=name,
+ description=description,
)
self.scheme_name = scheme_name or self.__class__.__name__
self.auto_error = auto_error
diff --git a/fastapi/security/http.py b/fastapi/security/http.py
index 8b677299d..8fc0aafd9 100644
--- a/fastapi/security/http.py
+++ b/fastapi/security/http.py
@@ -73,11 +73,6 @@ class HTTPBasic(HTTPBase):
unauthorized_headers = {"WWW-Authenticate": f'Basic realm="{self.realm}"'}
else:
unauthorized_headers = {"WWW-Authenticate": "Basic"}
- invalid_user_credentials_exc = HTTPException(
- status_code=HTTP_401_UNAUTHORIZED,
- detail="Invalid authentication credentials",
- headers=unauthorized_headers,
- )
if not authorization or scheme.lower() != "basic":
if self.auto_error:
raise HTTPException(
@@ -87,6 +82,11 @@ class HTTPBasic(HTTPBase):
)
else:
return None
+ invalid_user_credentials_exc = HTTPException(
+ status_code=HTTP_401_UNAUTHORIZED,
+ detail="Invalid authentication credentials",
+ headers=unauthorized_headers,
+ )
try:
data = b64decode(param).decode("ascii")
except (ValueError, UnicodeDecodeError, binascii.Error):
diff --git a/fastapi/security/oauth2.py b/fastapi/security/oauth2.py
index dc75dc9fe..938dec37c 100644
--- a/fastapi/security/oauth2.py
+++ b/fastapi/security/oauth2.py
@@ -1,4 +1,4 @@
-from typing import Any, Dict, List, Optional, Union
+from typing import Any, Dict, List, Optional, Union, cast
from fastapi.exceptions import HTTPException
from fastapi.openapi.models import OAuth2 as OAuth2Model
@@ -121,7 +121,9 @@ class OAuth2(SecurityBase):
description: Optional[str] = None,
auto_error: bool = True,
):
- self.model = OAuth2Model(flows=flows, description=description)
+ self.model = OAuth2Model(
+ flows=cast(OAuthFlowsModel, flows), description=description
+ )
self.scheme_name = scheme_name or self.__class__.__name__
self.auto_error = auto_error
@@ -148,7 +150,9 @@ class OAuth2PasswordBearer(OAuth2):
):
if not scopes:
scopes = {}
- flows = OAuthFlowsModel(password={"tokenUrl": tokenUrl, "scopes": scopes})
+ flows = OAuthFlowsModel(
+ password=cast(Any, {"tokenUrl": tokenUrl, "scopes": scopes})
+ )
super().__init__(
flows=flows,
scheme_name=scheme_name,
@@ -185,12 +189,15 @@ class OAuth2AuthorizationCodeBearer(OAuth2):
if not scopes:
scopes = {}
flows = OAuthFlowsModel(
- authorizationCode={
- "authorizationUrl": authorizationUrl,
- "tokenUrl": tokenUrl,
- "refreshUrl": refreshUrl,
- "scopes": scopes,
- }
+ authorizationCode=cast(
+ Any,
+ {
+ "authorizationUrl": authorizationUrl,
+ "tokenUrl": tokenUrl,
+ "refreshUrl": refreshUrl,
+ "scopes": scopes,
+ },
+ )
)
super().__init__(
flows=flows,
diff --git a/fastapi/utils.py b/fastapi/utils.py
index d8be53c57..9b9ebcb85 100644
--- a/fastapi/utils.py
+++ b/fastapi/utils.py
@@ -2,7 +2,18 @@ import re
import warnings
from dataclasses import is_dataclass
from enum import Enum
-from typing import TYPE_CHECKING, Any, Dict, Optional, Set, Type, Union, cast
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Dict,
+ MutableMapping,
+ Optional,
+ Set,
+ Type,
+ Union,
+ cast,
+)
+from weakref import WeakKeyDictionary
import fastapi
from fastapi.datastructures import DefaultPlaceholder, DefaultType
@@ -16,6 +27,11 @@ from pydantic.utils import lenient_issubclass
if TYPE_CHECKING: # pragma: nocover
from .routing import APIRoute
+# Cache for `create_cloned_field`
+_CLONED_TYPES_CACHE: MutableMapping[
+ Type[BaseModel], Type[BaseModel]
+] = WeakKeyDictionary()
+
def is_body_allowed_for_status_code(status_code: Union[int, str, None]) -> bool:
if status_code is None:
@@ -98,11 +114,13 @@ def create_response_field(
def create_cloned_field(
field: ModelField,
*,
- cloned_types: Optional[Dict[Type[BaseModel], Type[BaseModel]]] = None,
+ cloned_types: Optional[MutableMapping[Type[BaseModel], Type[BaseModel]]] = None,
) -> ModelField:
- # _cloned_types has already cloned types, to support recursive models
+ # cloned_types caches already cloned types to support recursive models and improve
+ # performance by avoiding unecessary cloning
if cloned_types is None:
- cloned_types = {}
+ cloned_types = _CLONED_TYPES_CACHE
+
original_type = field.type_
if is_dataclass(original_type) and hasattr(original_type, "__pydantic_model__"):
original_type = original_type.__pydantic_model__
diff --git a/pyproject.toml b/pyproject.toml
index 6aa095a64..5c0d3c48e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,5 +1,5 @@
[build-system]
-requires = ["hatchling"]
+requires = ["hatchling >= 1.13.0"]
build-backend = "hatchling.build"
[project]
@@ -41,57 +41,17 @@ classifiers = [
"Topic :: Internet :: WWW/HTTP",
]
dependencies = [
- "starlette>=0.26.1,<0.27.0",
- "pydantic >=1.6.2,!=1.7,!=1.7.1,!=1.7.2,!=1.7.3,!=1.8,!=1.8.1,<2.0.0",
+ "starlette>=0.27.0,<0.28.0",
+ "pydantic>=1.7.4,!=1.8,!=1.8.1,<2.0.0",
]
dynamic = ["version"]
[project.urls]
Homepage = "https://github.com/tiangolo/fastapi"
Documentation = "https://fastapi.tiangolo.com/"
+Repository = "https://github.com/tiangolo/fastapi"
[project.optional-dependencies]
-test = [
- "pytest >=7.1.3,<8.0.0",
- "coverage[toml] >= 6.5.0,< 8.0",
- "mypy ==0.982",
- "ruff ==0.0.138",
- "black == 23.1.0",
- "isort >=5.0.6,<6.0.0",
- "httpx >=0.23.0,<0.24.0",
- "email_validator >=1.1.1,<2.0.0",
- # TODO: once removing databases from tutorial, upgrade SQLAlchemy
- # probably when including SQLModel
- "sqlalchemy >=1.3.18,<1.4.43",
- "peewee >=3.13.3,<4.0.0",
- "databases[sqlite] >=0.3.2,<0.7.0",
- "orjson >=3.2.1,<4.0.0",
- "ujson >=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0",
- "python-multipart >=0.0.5,<0.0.7",
- "flask >=1.1.2,<3.0.0",
- "anyio[trio] >=3.2.1,<4.0.0",
- "python-jose[cryptography] >=3.3.0,<4.0.0",
- "pyyaml >=5.3.1,<7.0.0",
- "passlib[bcrypt] >=1.7.2,<2.0.0",
-
- # types
- "types-ujson ==5.7.0.1",
- "types-orjson ==3.6.2",
-]
-doc = [
- "mkdocs >=1.1.2,<2.0.0",
- "mkdocs-material >=8.1.4,<9.0.0",
- "mdx-include >=1.4.1,<2.0.0",
- "mkdocs-markdownextradata-plugin >=0.1.7,<0.3.0",
- "typer-cli >=0.0.13,<0.0.14",
- "typer[all] >=0.6.1,<0.8.0",
- "pyyaml >=5.3.1,<7.0.0",
-]
-dev = [
- "ruff ==0.0.138",
- "uvicorn[standard] >=0.12.0,<0.21.0",
- "pre-commit >=2.17.0,<3.0.0",
-]
all = [
"httpx >=0.23.0",
"jinja2 >=2.11.2",
@@ -107,10 +67,6 @@ all = [
[tool.hatch.version]
path = "fastapi/__init__.py"
-[tool.isort]
-profile = "black"
-known_third_party = ["fastapi", "pydantic", "starlette"]
-
[tool.mypy]
strict = true
@@ -166,7 +122,7 @@ select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
- # "I", # isort
+ "I", # isort
"C", # flake8-comprehensions
"B", # flake8-bugbear
]
diff --git a/requirements-docs.txt b/requirements-docs.txt
new file mode 100644
index 000000000..e9d0567ed
--- /dev/null
+++ b/requirements-docs.txt
@@ -0,0 +1,8 @@
+-e .
+mkdocs >=1.1.2,<2.0.0
+mkdocs-material >=8.1.4,<9.0.0
+mdx-include >=1.4.1,<2.0.0
+mkdocs-markdownextradata-plugin >=0.1.7,<0.3.0
+typer-cli >=0.0.13,<0.0.14
+typer[all] >=0.6.1,<0.8.0
+pyyaml >=5.3.1,<7.0.0
diff --git a/requirements-tests.txt b/requirements-tests.txt
new file mode 100644
index 000000000..3ef3c4fd9
--- /dev/null
+++ b/requirements-tests.txt
@@ -0,0 +1,25 @@
+-e .
+pytest >=7.1.3,<8.0.0
+coverage[toml] >= 6.5.0,< 8.0
+mypy ==1.3.0
+ruff ==0.0.272
+black == 23.3.0
+httpx >=0.23.0,<0.24.0
+email_validator >=1.1.1,<2.0.0
+# TODO: once removing databases from tutorial, upgrade SQLAlchemy
+# probably when including SQLModel
+sqlalchemy >=1.3.18,<1.4.43
+peewee >=3.13.3,<4.0.0
+databases[sqlite] >=0.3.2,<0.7.0
+orjson >=3.2.1,<4.0.0
+ujson >=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0
+python-multipart >=0.0.5,<0.0.7
+flask >=1.1.2,<3.0.0
+anyio[trio] >=3.2.1,<4.0.0
+python-jose[cryptography] >=3.3.0,<4.0.0
+pyyaml >=5.3.1,<7.0.0
+passlib[bcrypt] >=1.7.2,<2.0.0
+
+# types
+types-ujson ==5.7.0.1
+types-orjson ==3.6.2
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 000000000..cb9abb44a
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,5 @@
+-e .[all]
+-r requirements-tests.txt
+-r requirements-docs.txt
+uvicorn[standard] >=0.12.0,<0.21.0
+pre-commit >=2.17.0,<3.0.0
diff --git a/scripts/build-docs.sh b/scripts/build-docs.sh
index 383ad3f44..ebf864afa 100755
--- a/scripts/build-docs.sh
+++ b/scripts/build-docs.sh
@@ -3,4 +3,6 @@
set -e
set -x
+# Check README.md is up to date
+python ./scripts/docs.py verify-readme
python ./scripts/docs.py build-all
diff --git a/scripts/format.sh b/scripts/format.sh
index 3ac1fead8..3fb3eb4f1 100755
--- a/scripts/format.sh
+++ b/scripts/format.sh
@@ -3,4 +3,3 @@ set -x
ruff fastapi tests docs_src scripts --fix
black fastapi tests docs_src scripts
-isort fastapi tests docs_src scripts
diff --git a/scripts/lint.sh b/scripts/lint.sh
index 0feb973a8..4db5caa96 100755
--- a/scripts/lint.sh
+++ b/scripts/lint.sh
@@ -6,4 +6,3 @@ set -x
mypy fastapi
ruff fastapi tests docs_src scripts
black fastapi tests --check
-isort fastapi tests docs_src scripts --check-only
diff --git a/scripts/test.sh b/scripts/test.sh
index 62449ea41..7d17add8f 100755
--- a/scripts/test.sh
+++ b/scripts/test.sh
@@ -3,7 +3,5 @@
set -e
set -x
-# Check README.md is up to date
-python ./scripts/docs.py verify-readme
export PYTHONPATH=./docs_src
coverage run -m pytest tests ${@}
diff --git a/tests/test_empty_router.py b/tests/test_empty_router.py
index 186ceb347..1a40cbe30 100644
--- a/tests/test_empty_router.py
+++ b/tests/test_empty_router.py
@@ -1,5 +1,6 @@
import pytest
from fastapi import APIRouter, FastAPI
+from fastapi.exceptions import FastAPIError
from fastapi.testclient import TestClient
app = FastAPI()
@@ -31,5 +32,5 @@ def test_use_empty():
def test_include_empty():
# if both include and router.path are empty - it should raise exception
- with pytest.raises(Exception):
+ with pytest.raises(FastAPIError):
app.include_router(router)
diff --git a/tests/test_param_include_in_schema.py b/tests/test_param_include_in_schema.py
index cb182a1cd..d0c29f7b2 100644
--- a/tests/test_param_include_in_schema.py
+++ b/tests/test_param_include_in_schema.py
@@ -33,7 +33,7 @@ async def hidden_query(
return {"hidden_query": hidden_query}
-openapi_shema = {
+openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
@@ -162,7 +162,7 @@ def test_openapi_schema():
client = TestClient(app)
response = client.get("/openapi.json")
assert response.status_code == 200
- assert response.json() == openapi_shema
+ assert response.json() == openapi_schema
@pytest.mark.parametrize(
diff --git a/tests/test_router_redirect_slashes.py b/tests/test_router_redirect_slashes.py
new file mode 100644
index 000000000..086665c04
--- /dev/null
+++ b/tests/test_router_redirect_slashes.py
@@ -0,0 +1,40 @@
+from fastapi import APIRouter, FastAPI
+from fastapi.testclient import TestClient
+
+
+def test_redirect_slashes_enabled():
+ app = FastAPI()
+ router = APIRouter()
+
+ @router.get("/hello/")
+ def hello_page() -> str:
+ return "Hello, World!"
+
+ app.include_router(router)
+
+ client = TestClient(app)
+
+ response = client.get("/hello/", follow_redirects=False)
+ assert response.status_code == 200
+
+ response = client.get("/hello", follow_redirects=False)
+ assert response.status_code == 307
+
+
+def test_redirect_slashes_disabled():
+ app = FastAPI(redirect_slashes=False)
+ router = APIRouter()
+
+ @router.get("/hello/")
+ def hello_page() -> str:
+ return "Hello, World!"
+
+ app.include_router(router)
+
+ client = TestClient(app)
+
+ response = client.get("/hello/", follow_redirects=False)
+ assert response.status_code == 200
+
+ response = client.get("/hello", follow_redirects=False)
+ assert response.status_code == 404
diff --git a/tests/test_schema_extra_examples.py b/tests/test_schema_extra_examples.py
index 74e15d59a..41021a983 100644
--- a/tests/test_schema_extra_examples.py
+++ b/tests/test_schema_extra_examples.py
@@ -42,7 +42,7 @@ def examples(
@app.post("/example_examples/")
def example_examples(
item: Item = Body(
- example={"data": "Overriden example"},
+ example={"data": "Overridden example"},
examples={
"example1": {"value": {"data": "examples example_examples 1"}},
"example2": {"value": {"data": "examples example_examples 2"}},
@@ -76,7 +76,7 @@ def example_examples(
# def form_example_examples(
# lastname: str = Form(
# ...,
-# example="Doe overriden",
+# example="Doe overridden",
# examples={
# "example1": {"summary": "last name summary", "value": "Doe"},
# "example2": {"value": "Doesn't"},
@@ -110,7 +110,7 @@ def path_examples(
@app.get("/path_example_examples/{item_id}")
def path_example_examples(
item_id: str = Path(
- example="item_overriden",
+ example="item_overridden",
examples={
"example1": {"summary": "item ID summary", "value": "item_1"},
"example2": {"value": "item_2"},
@@ -147,7 +147,7 @@ def query_examples(
def query_example_examples(
data: Union[str, None] = Query(
default=None,
- example="query_overriden",
+ example="query_overridden",
examples={
"example1": {"summary": "Query example 1", "value": "query1"},
"example2": {"value": "query2"},
@@ -184,7 +184,7 @@ def header_examples(
def header_example_examples(
data: Union[str, None] = Header(
default=None,
- example="header_overriden",
+ example="header_overridden",
examples={
"example1": {"summary": "Query example 1", "value": "header1"},
"example2": {"value": "header2"},
@@ -221,7 +221,7 @@ def cookie_examples(
def cookie_example_examples(
data: Union[str, None] = Cookie(
default=None,
- example="cookie_overriden",
+ example="cookie_overridden",
examples={
"example1": {"summary": "Query example 1", "value": "cookie1"},
"example2": {"value": "cookie2"},
diff --git a/tests/test_ws_dependencies.py b/tests/test_ws_dependencies.py
new file mode 100644
index 000000000..ccb1c4b7d
--- /dev/null
+++ b/tests/test_ws_dependencies.py
@@ -0,0 +1,73 @@
+import json
+from typing import List
+
+from fastapi import APIRouter, Depends, FastAPI, WebSocket
+from fastapi.testclient import TestClient
+from typing_extensions import Annotated
+
+
+def dependency_list() -> List[str]:
+ return []
+
+
+DepList = Annotated[List[str], Depends(dependency_list)]
+
+
+def create_dependency(name: str):
+ def fun(deps: DepList):
+ deps.append(name)
+
+ return Depends(fun)
+
+
+router = APIRouter(dependencies=[create_dependency("router")])
+prefix_router = APIRouter(dependencies=[create_dependency("prefix_router")])
+app = FastAPI(dependencies=[create_dependency("app")])
+
+
+@app.websocket("/", dependencies=[create_dependency("index")])
+async def index(websocket: WebSocket, deps: DepList):
+ await websocket.accept()
+ await websocket.send_text(json.dumps(deps))
+ await websocket.close()
+
+
+@router.websocket("/router", dependencies=[create_dependency("routerindex")])
+async def routerindex(websocket: WebSocket, deps: DepList):
+ await websocket.accept()
+ await websocket.send_text(json.dumps(deps))
+ await websocket.close()
+
+
+@prefix_router.websocket("/", dependencies=[create_dependency("routerprefixindex")])
+async def routerprefixindex(websocket: WebSocket, deps: DepList):
+ await websocket.accept()
+ await websocket.send_text(json.dumps(deps))
+ await websocket.close()
+
+
+app.include_router(router, dependencies=[create_dependency("router2")])
+app.include_router(
+ prefix_router, prefix="/prefix", dependencies=[create_dependency("prefix_router2")]
+)
+
+
+def test_index():
+ client = TestClient(app)
+ with client.websocket_connect("/") as websocket:
+ data = json.loads(websocket.receive_text())
+ assert data == ["app", "index"]
+
+
+def test_routerindex():
+ client = TestClient(app)
+ with client.websocket_connect("/router") as websocket:
+ data = json.loads(websocket.receive_text())
+ assert data == ["app", "router2", "router", "routerindex"]
+
+
+def test_routerprefixindex():
+ client = TestClient(app)
+ with client.websocket_connect("/prefix/") as websocket:
+ data = json.loads(websocket.receive_text())
+ assert data == ["app", "prefix_router2", "prefix_router", "routerprefixindex"]
diff --git a/tests/test_ws_router.py b/tests/test_ws_router.py
index c312821e9..240a42bb0 100644
--- a/tests/test_ws_router.py
+++ b/tests/test_ws_router.py
@@ -1,4 +1,16 @@
-from fastapi import APIRouter, Depends, FastAPI, WebSocket
+import functools
+
+import pytest
+from fastapi import (
+ APIRouter,
+ Depends,
+ FastAPI,
+ Header,
+ WebSocket,
+ WebSocketDisconnect,
+ status,
+)
+from fastapi.middleware import Middleware
from fastapi.testclient import TestClient
router = APIRouter()
@@ -63,9 +75,44 @@ async def router_native_prefix_ws(websocket: WebSocket):
await websocket.close()
-app.include_router(router)
-app.include_router(prefix_router, prefix="/prefix")
-app.include_router(native_prefix_route)
+async def ws_dependency_err():
+ raise NotImplementedError()
+
+
+@router.websocket("/depends-err/")
+async def router_ws_depends_err(websocket: WebSocket, data=Depends(ws_dependency_err)):
+ pass # pragma: no cover
+
+
+async def ws_dependency_validate(x_missing: str = Header()):
+ pass # pragma: no cover
+
+
+@router.websocket("/depends-validate/")
+async def router_ws_depends_validate(
+ websocket: WebSocket, data=Depends(ws_dependency_validate)
+):
+ pass # pragma: no cover
+
+
+class CustomError(Exception):
+ pass
+
+
+@router.websocket("/custom_error/")
+async def router_ws_custom_error(websocket: WebSocket):
+ raise CustomError()
+
+
+def make_app(app=None, **kwargs):
+ app = app or FastAPI(**kwargs)
+ app.include_router(router)
+ app.include_router(prefix_router, prefix="/prefix")
+ app.include_router(native_prefix_route)
+ return app
+
+
+app = make_app(app)
def test_app():
@@ -125,3 +172,100 @@ def test_router_with_params():
assert data == "path/to/file"
data = websocket.receive_text()
assert data == "a_query_param"
+
+
+def test_wrong_uri():
+ """
+ Verify that a websocket connection to a non-existent endpoing returns in a shutdown
+ """
+ client = TestClient(app)
+ with pytest.raises(WebSocketDisconnect) as e:
+ with client.websocket_connect("/no-router/"):
+ pass # pragma: no cover
+ assert e.value.code == status.WS_1000_NORMAL_CLOSURE
+
+
+def websocket_middleware(middleware_func):
+ """
+ Helper to create a Starlette pure websocket middleware
+ """
+
+ def middleware_constructor(app):
+ @functools.wraps(app)
+ async def wrapped_app(scope, receive, send):
+ if scope["type"] != "websocket":
+ return await app(scope, receive, send) # pragma: no cover
+
+ async def call_next():
+ return await app(scope, receive, send)
+
+ websocket = WebSocket(scope, receive=receive, send=send)
+ return await middleware_func(websocket, call_next)
+
+ return wrapped_app
+
+ return middleware_constructor
+
+
+def test_depend_validation():
+ """
+ Verify that a validation in a dependency invokes the correct exception handler
+ """
+ caught = []
+
+ @websocket_middleware
+ async def catcher(websocket, call_next):
+ try:
+ return await call_next()
+ except Exception as e: # pragma: no cover
+ caught.append(e)
+ raise
+
+ myapp = make_app(middleware=[Middleware(catcher)])
+
+ client = TestClient(myapp)
+ with pytest.raises(WebSocketDisconnect) as e:
+ with client.websocket_connect("/depends-validate/"):
+ pass # pragma: no cover
+ # the validation error does produce a close message
+ assert e.value.code == status.WS_1008_POLICY_VIOLATION
+ # and no error is leaked
+ assert caught == []
+
+
+def test_depend_err_middleware():
+ """
+ Verify that it is possible to write custom WebSocket middleware to catch errors
+ """
+
+ @websocket_middleware
+ async def errorhandler(websocket: WebSocket, call_next):
+ try:
+ return await call_next()
+ except Exception as e:
+ await websocket.close(code=status.WS_1006_ABNORMAL_CLOSURE, reason=repr(e))
+
+ myapp = make_app(middleware=[Middleware(errorhandler)])
+ client = TestClient(myapp)
+ with pytest.raises(WebSocketDisconnect) as e:
+ with client.websocket_connect("/depends-err/"):
+ pass # pragma: no cover
+ assert e.value.code == status.WS_1006_ABNORMAL_CLOSURE
+ assert "NotImplementedError" in e.value.reason
+
+
+def test_depend_err_handler():
+ """
+ Verify that it is possible to write custom WebSocket middleware to catch errors
+ """
+
+ async def custom_handler(websocket: WebSocket, exc: CustomError) -> None:
+ await websocket.close(1002, "foo")
+
+ myapp = make_app(exception_handlers={CustomError: custom_handler})
+ client = TestClient(myapp)
+ with pytest.raises(WebSocketDisconnect) as e:
+ with client.websocket_connect("/custom_error/"):
+ pass # pragma: no cover
+ assert e.value.code == 1002
+ assert "foo" in e.value.reason