🌐 Add Ukrainian translation for `docs/uk/docs/tutorial/response-model.md`

[USER_REDACTED] [USER_REDACTED] [USER_REDACTED] [USER_REDACTED] please take a look 😄

@@ -0,0 +1,358 @@
+# Модель відповіді — Тип, що повертається
+
+Ви можете оголосити тип, який використовуватиметься у відповіді, за допомогою *анотації типу, що повертається* *функцією операцією шляху* (path operation)
+
+**Анотацію типу** можна вказати так само як і для вхідних **параметрів** функції: це можуть бути моделі Pydantic, списки (lists), словники (dictionaries), скалярні значення, як-от цілі числа (integers), булеві значення (booleans) тощо.
+
+{* ../../docs_src/response_model/tutorial001_01_py310.py hl[16,21] *}
+
+FastAPI використовуватиме цей тип, щоб:
+
+* **Перевірити правильність** повернених даних.
+    * Якщо дані не валідні (наприклад, відсутнє поле), це означає, що Ваш код додатку працює некоректно і не повертає те, що повинен. У такому випадку FastAPI поверне помилку сервера, замість того щоб віддати недопустимі дані. Так Ви та Ваші клієнти будете впевнені, що отримуєте очікувані дані у правильному форматі.
+
+* Додати **JSON Schema** відповіді до специфікації OpenAPI в *операціях шляху*.
+    * Це буде використано в **автоматичній документації**.
+    * А також інструментами, які автоматично генерують клієнтський код.
+
+Але найголовніше:
+
+* FastAPI **обмежить та відфільтрує** вихідні дані відповідно до типу, вказаного у відповіді.
+    * Це особливо важливо для **безпеки**. Деталі нижче.
+
+## Параметр `response_model`
+
+Іноді Вам потрібно або зручно повертати інші типи даних, ніж ті, що зазначені як тип відповіді.
+
+Наприклад, Ви можете **повертати словник** або об’єкт бази даних, але **оголосити модель Pydantic** як модель відповіді. Тоді модель Pydantic автоматично оброблятиме валідацію, документацію тощо.
+
+Якщо Ви додасте анотацію типу для повернення, редактор коду або mypy можуть поскаржитися, що функція повертає інший тип (наприклад, dict замість Item).
+
+У таких випадках можна скористатися параметром `response_model` в декораторі маршруту (наприклад, [USER_REDACTED].get()).
+
+Параметр `response_model` працює з будь-яким *оператором шляху*:
+
+* `[USER_REDACTED].get()`
+* `[USER_REDACTED].post()`
+* `[USER_REDACTED].put()`
+* `[USER_REDACTED].delete()`
+* тощо.
+
+{* ../../docs_src/response_model/tutorial001_py310.py hl[17,22,24:27] *}
+
+/// note | Примітка
+
+Зверніть увагу, що `response_model` є параметром методу-декоратора (`get`, `post`, тощо), а не *функцією операцією шляху* (path operation function), як це робиться з параметрами або тілом запиту.
+
+///
+
+`response_model` приймає такий самий тип, який Ви б вказали для поля моделі Pydantic. Тобто це може бути як Pydantic-модель, так і, наприклад, `list` із моделей Pydantic — `List[Item]`.
+
+FastAPI використовуватиме `response_model` для створення документації, валідації даних та — найважливіше — **перетворення та фільтрації вихідних даних** згідно з оголошеним типом.
+
+/// tip | Порада
+
+Якщо у Вас увімкнено сувору перевірку типів у редакторі, mypy тощо, Ви можете оголосити тип повернення функції як `Any`.
+
+Таким чином, Ви повідомляєте редактору, що свідомо повертаєте будь-що. Але FastAPI усе одно виконуватиме створення документації, валідацію, фільтрацію тощо за допомогою параметра `response_model`.
+
+///
+
+### Пріоритет `response_model`
+
+Якщо Ви вказуєте і тип повернення, і `response_model`, то FastAPI використовуватиме `response_model` з пріоритетом.
+
+Таким чином, Ви можете додати правильні анотації типів до ваших функцій, навіть якщо вони повертають тип, відмінний від `response_model`. Це буде корисно для редакторів коду та інструментів, таких як mypy. І при цьому FastAPI продовжить виконувати валідацію даних, генерувати документацію тощо на основі `response_model`.
+
+Ви також можете використати `response_model=None`, щоб вимкнути створення моделі відповіді для цієї *операції шляху*. Це може знадобитися, якщо Ви додаєте анотації типів до об'єктів, які не є допустимими полями Pydantic — приклад цього Ви побачите в одному з наступних розділів.
+
+## Повернути ті самі вхідні дані
+
+Тут ми оголошуємо модель `UserIn`, яка містить звичайний текстовий пароль:
+
+{* ../../docs_src/response_model/tutorial002_py310.py hl[7,9] *}
+
+/// info | Інформація
+
+Щоб використовувати `EmailStr`, спочатку встановіть `email-validator`.
+
+Переконайтесь, що Ви створили [віртуальне середовище](../virtual-environments.md){.internal-link target=_blank}, активували його, а потім встановили пакет, наприклад:
+
+```console
+$ pip install email-validator
+```
+
+or with:
+
+```console
+$ pip install "pydantic[email]"
+```
+
+///
+
+І ми використовуємо цю модель, щоб оголосити і вхідні, і вихідні дані:
+
+{* ../../docs_src/response_model/tutorial002_py310.py hl[16] *}
+
+Тепер, коли браузер створює користувача з паролем, API поверне той самий пароль у відповіді.
+
+У цьому випадку це може не бути проблемою, адже саме користувач надіслав пароль.
+
+Але якщо ми використаємо цю ж модель для іншої операції шляху, ми можемо випадково надіслати паролі наших користувачів кожному клієнту.
+
+/// danger | Обережно
+
+Ніколи не зберігайте пароль користувача у відкритому вигляді та не надсилайте його у відповіді, якщо тільки Ви не знаєте всі ризики і точно розумієте, що робите.
+
+///
+
+## Додайте окрему вихідну модель
+
+Замість цього ми можемо створити вхідну модель з відкритим паролем і вихідну модель без нього:
+
+{* ../../docs_src/response_model/tutorial003_py310.py hl[9,11,16] *}
+
+Тут, навіть якщо *функція операції шляху* повертає об'єкт користувача, який містить пароль:
+
+{* ../../docs_src/response_model/tutorial003_py310.py hl[24] *}
+
+...ми оголосили `response_model` як нашу модель `UserOut`, яка не містить пароля:
+
+{* ../../docs_src/response_model/tutorial003_py310.py hl[22] *}
+
+Таким чином, **FastAPI** автоматично відфільтрує всі дані, які не вказані у вихідній моделі (за допомогою Pydantic).
+
+### `response_model` або тип повернення
+
+У цьому випадку, оскільки дві моделі різні, якщо ми анотуємо тип повернення функції як `UserOut`, редактор і такі інструменти, як mypy, видадуть помилку, бо фактично ми повертаємо інший тип.
+
+Тому в цьому прикладі ми використовуємо параметр `response_model`, а не анотацію типу повернення.
+
+...але читайте далі, щоб дізнатися, як обійти це обмеження.
+
+## Тип повернення і фільтрація даних
+
+Продовжимо з попереднього прикладу. Ми хотіли **анотувати функцію одним типом**, але при цьому повертати з неї більше даних.
+
+Ми хочемо, щоб FastAPI продовжував **фільтрувати** ці дані за допомогою response_model. Тобто навіть якщо функція повертає більше інформації, у відповіді будуть лише ті поля, які вказані у response_model.
+
+У попередньому прикладі, оскільки класи були різні, нам довелося використовувати параметр `response_model`. Але це означає, що ми не отримуємо підтримки з боку редактора коду та інструментів перевірки типів щодо типу, який повертає функція.
+
+Проте в більшості випадків, коли нам потрібно зробити щось подібне, ми просто хочемо, щоб модель **відфільтрувала або прибрала** частину даних, як у цьому прикладі.
+
+У таких випадках ми можемо використати класи та спадкування, щоб скористатися **анотаціями типів** функцій — це дає кращу підтримку з боку редактора та інструментів типу mypy, і при цьому FastAPI продовжує виконувати **фільтрацію даних** у відповіді.
+
+{* ../../docs_src/response_model/tutorial003_01_py310.py hl[7:10,13:14,18] *}
+
+Завдяки цьому ми отримуємо підтримку інструментів — від редакторів і mypy, оскільки цей код є коректним з точки зору типів, — але ми також отримуємо фільтрацію даних від FastAPI.
+
+Як це працює? Давайте розберемося. 🤓
+
+### Типи та підтримка інструментів
+
+Спершу подивимось, як це бачать редактори, mypy та інші інструменти.
+
+`BaseUser` має базові поля. Потім `UserIn` успадковує `BaseUser` і додає поле `password`, отже, він матиме всі поля з обох моделей.
+
+Ми зазначаємо тип повернення функції як `BaseUser`, але фактично повертаємо екземпляр `UserIn`.
+
+Редактор, mypy та інші інструменти не скаржитимуться на це, тому що з точки зору типізації `UserIn` є підкласом `BaseUser`, а це означає, що він є `валідним` типом, коли очікується будь-що, що є `BaseUser`.
+
+### Фільтрація даних у FastAPI
+
+Тепер для FastAPI він бачить тип повернення і переконується, що те, що Ви повертаєте, містить **тільки** поля, які оголошені у цьому типі.
+
+FastAPI виконує кілька внутрішніх операцій з Pydantic, щоб гарантувати, що правила наслідування класів не застосовуються для фільтрації повернених даних, інакше Ви могли б повернути значно більше даних, ніж очікували.
+
+Таким чином, Ви отримуєте найкраще з двох світів: анотації типів **з підтримкою інструментів** і **фільтрацію даних**.
+
+## Подивитись у документації
+
+Коли Ви дивитесь автоматичну документацію, Ви можете побачити, що вхідна модель і вихідна модель мають власну JSON-схему:
+
+
+
+І обидві моделі використовуються для інтерактивної API-документації:
+
+
+
+## Інші анотації типів повернення
+
+Існують випадки, коли Ви повертаєте щось, що не є допустимим полем Pydantic, але анотуєте це у функції лише для того, щоб отримати підтримку від інструментів (редактора, mypy тощо).
+
+### Повернення Response напряму
+
+Найпоширенішим випадком буде [повернення Response напряму, як пояснюється пізніше у розширеній документації](../advanced/response-directly.md){.internal-link target=_blank}.
+
+{* ../../docs_src/response_model/tutorial003_02.py hl[8,10:11] *}
+
+Цей простий випадок автоматично обробляється FastAPI, тому що анотація типу повернення — це клас (або підклас) `Response`.
+
+І інструменти також будуть задоволені, бо і `RedirectResponse`, і `JSONResponse` є підкласами `Response`, отже анотація типу коректна.
+
+### Анотація підкласу Response
+
+Також можна використовувати підклас `Response` у анотації типу:
+
+{* ../../docs_src/response_model/tutorial003_03.py hl[8:9] *}
+
+Це теж працюватиме, бо `RedirectResponse` — підклас `Response`, і FastAPI автоматично обробить цей простий випадок.
+
+### Некоректні анотації типу повернення
+
+Але коли Ви повертаєте якийсь інший довільний об’єкт, що не є валідним типом Pydantic (наприклад, об’єкт бази даних), і анотуєте його так у функції, FastAPI спробує створити Pydantic модель відповіді на основі цієї анотації типу, і це завершиться помилкою.
+
+Те саме станеться, якщо Ви використовуєте union між різними типами, де один або більше не є валідними типами Pydantic, наприклад, це спричинить помилку 💥:
+
+{* ../../docs_src/response_model/tutorial003_04_py310.py hl[8] *}
+
+...це не працює, тому що тип анотації не є типом Pydantic і не є просто класом `Response` або його підкласом, а є об’єднанням (union) — або `Response`, або `dict`.
+
+### Відключення Моделі Відповіді
+
+Продовжуючи приклад вище, можливо, Ви не хочете використовувати стандартну валідацію даних, автоматичну документацію, фільтрацію тощо, які FastAPI виконує за замовчуванням.
+
+Але ви все одно можете залишити анотацію типу у функції, щоб зберегти підтримку з боку інструментів, таких як редактори коду або статичні перевірки типів (наприклад, mypy).
+
+У такому випадку ви можете вимкнути генерацію моделі відповіді, встановивши `response_model=None`:
+
+{* ../../docs_src/response_model/tutorial003_05_py310.py hl[7] *}
+
+Це змусить FastAPI пропустити генерацію моделі відповіді, і таким чином Ви зможете використовувати будь-які анотації типів повернення без впливу на вашу FastAPI аплікацію. 🤓
+
+## Параметри кодування моделі відповіді
+
+Ваша модель відповіді може мати значення за замовчуванням, наприклад:
+
+{* ../../docs_src/response_model/tutorial004_py310.py hl[9,11:12] *}
+
+* `description: Union[str, None] = None` (або `str | None = None` у Python 3.10) має значення за замовчуванням `None`.
+* `tax: float = 10.5` має значення за замовчуванням `10.5`.
+* `tags: List[str] = []` має значення за замовчуванням порожній список: `[]`.
+
+Але Ви можете захотіти не включати їх у результат, якщо вони фактично не були збережені.
+
+Наприклад, якщо у Вас є моделі з багатьма необов’язковими атрибутами у NoSQL базі даних, але Ви не хочете відправляти дуже довгі JSON-відповіді, повні значень за замовчуванням.
+
+### Використовуйте параметр `response_model_exclude_unset`
+
+Ви можете встановити параметр декоратора шляху `response_model_exclude_unset=True`:
+
+{* ../../docs_src/response_model/tutorial004_py310.py hl[22] *}
+
+і ці значення за замовчуванням не будуть включені у відповідь, тільки фактично встановлені значення.
+
+Отже, якщо Ви надішлете запит до цього оператора шляху для елемента з item_id `foo`, відповідь (без включення значень за замовчуванням) буде:
+
+```JSON
+{
+    "name": "Foo",
+    "price": 50.2
+}
+```
+
+/// info | Інформація
+
+У Pydantic версії 1 метод називався `.dict()`, він був застарілий (але ще підтримується) у Pydantic версії 2 і перейменований у `.model_dump()`.
+
+Приклади тут використовують `.dict()` для сумісності з Pydantic v1, але Вам слід використовувати `.model_dump()`, якщо Ви можете використовувати Pydantic v2.
+
+///
+
+/// info | Інформація
+
+FastAPI використовує `.dict()` моделі Pydantic з параметром `exclude_unset`, щоб досягти цього.
+
+///
+
+/// info | Інформація
+
+Ви також можете використовувати:
+
+* `response_model_exclude_defaults=True`
+* `response_model_exclude_none=True`
+
+як описано в документації Pydantic for `exclude_defaults` та `exclude_none`.
+
+///
+
+#### Дані зі значеннями для полів із типовими значеннями
+
+Але якщо Ваші дані мають значення для полів моделі з типовими значеннями, як у елемента з item_id `bar`:
+
+```Python hl_lines="3  5"
+{
+    "name": "Bar",
+    "description": "The bartenders",
+    "price": 62,
+    "tax": 20.2
+}
+```
+вони будуть включені у відповідь.
+
+#### Дані з тими самими значеннями, що й типові
+
+Якщо дані мають ті самі значення, що й типові, як у елемента з item_id `baz`:
+
+```Python hl_lines="3  5-6"
+{
+    "name": "Baz",
+    "description": None,
+    "price": 50.2,
+    "tax": 10.5,
+    "tags": []
+}
+```
+
+FastAPI достатньо розумний (насправді, Pydantic достатньо розумний), щоб зрозуміти, що, хоча `description`, `tax` і `tags` мають ті самі значення, що й типові, вони були встановлені явно (а не взяті як значення за замовчуванням).
+
+Отже, вони будуть включені у JSON-відповідь.
+
+/// tip | Порада
+
+Зверніть увагу, що типові значення можуть бути будь-якими, не лише `None`.
+
+Це може бути list (`[]`), `float` 10.5 тощо.
+
+///
+
+### `response_model_include` та `response_model_exclude`
+
+Ви також можете використовувати параметри *декоратора операції шляху* `response_model_include` та `response_model_exclude`.
+
+Вони приймають `set` (множину) рядків (`str`) з іменами атрибутів, які потрібно включити (пропускаючи інші) або виключити (включаючи інші).
+
+Це можна використовувати як швидкий спосіб, якщо у Вас є лише одна модель Pydantic і Ви хочете видалити деякі дані з виводу.
+
+/// tip | Порада
+
+Але все ж рекомендується використовувати описані вище підходи, із застосуванням кількох класів, замість цих параметрів.
+
+
+Це тому, що JSON Schema, який генерується у вашому OpenAPI додатку (і в документації), все одно буде відповідати повній моделі, навіть якщо Ви використовуєте `response_model_include` або `response_model_exclude` для виключення деяких атрибутів.
+
+Це також стосується `response_model_by_alias`, який працює подібним чином.
+
+///
+
+{* ../../docs_src/response_model/tutorial005_py310.py hl[29,35] *}
+
+/// tip | Порада
+
+Синтаксис `{"name", "description"}` створює `set` з цими двома значеннями.
+
+Він еквівалентний `set(["name", "description"])`.
+
+///
+
+#### Використання `list` замість `set`
+
+Якщо Ви забудете використати `set` і натомість застосуєте `list` або `tuple`, FastAPI все одно перетворить це на `set`, і все працюватиме правильно:
+
+{* ../../docs_src/response_model/tutorial006_py310.py hl[29,35] *}
+
+## Підсумок
+
+Використовуйте параметр `response_model` *декоратора операції шляху*, щоб визначати моделі відповіді, особливо щоб гарантувати фільтрацію приватних даних.
+
+Використовуйте `response_model_exclude_unset`, щоб повертати лише явно встановлені значення.

Looks good, approved

Дозвольте долучитись до локалізаційного свята? :)

Please, be more careful with links

LGTM!

```suggestion
Переконайтесь, що Ви створили [віртуальне середовище](../virtual-environments.md){.internal-link target=_blank}, активували його, а потім встановили пакет, наприклад:
```

```suggestion
Ми хочемо, щоб FastAPI продовжував **фільтрувати** ці дані за допомогою response_model. Тобто навіть якщо функція повертає більше інформації, у відповіді будуть лише ті поля, які вказані у response_model.
```

```suggestion
Редактор, mypy та інші інструменти не скаржитимуться на це, тому що з точки зору типізації `UserIn` є підкласом `BaseUser`, а це означає, що він є _валідним_ типом, коли очікується будь-що, що є `BaseUser`.
```

```suggestion
Найпоширенішим випадком буде [повернення Response напряму, як пояснюється пізніше у розширеній документації](../advanced/response-directly.md){.internal-link target=_blank}.
```

Title of the 'abbr' can also be translated

недопустимі/неправильні?

той же комент - можливо не допустимим полем буде кращим варіантом


🌐 Add Ukrainian translation for `docs/uk/docs/tutorial/query-param-models.md`

@@ -0,0 +1,68 @@
+# Моделі Query параметрів
+
+Якщо у Вас є група **query параметрів**, які пов’язані між собою, Ви можете створити **Pydantic-модель** для їх оголошення.
+
+Це дозволить Вам **повторно використовувати модель** у **різних місцях**, а також оголошувати перевірки та метадані для всіх параметрів одночасно. 😎
+
+/// note | Примітка
+
+Ця можливість підтримується, починаючи з версії FastAPI `0.115.0`. 🤓
+
+///
+
+## Query параметри з Pydantic-моделлю
+
+Оголосіть **query параметри**, які Вам потрібні, у **Pydantic-моделі**, а потім оголосіть цей параметр як `Query`:
+
+{* ../../docs_src/query_param_models/tutorial001_an_py310.py hl[9:13,17] *}
+
+**FastAPI**  буде **витягувати** дані для **кожного поля** з **query параметрів** у запиті та передавати їх у визначену вами Pydantic-модель.
+
+## Перевірте документацію
+
+Ви можете побачити параметри запиту в UI документації за `/docs`:
+
+

+
+

+
+## Заборона зайвих Query параметрів
+
+У деяких особливих випадках (ймовірно, не дуже поширених) Ви можете захотіти **обмежити** query параметри, які дозволено отримувати.
+
+Ви можете використати конфігурацію моделі Pydantic, щоб заборонити (`forbid`) будь-які зайві (`extra`) поля:
+
+{* ../../docs_src/query_param_models/tutorial002_an_py310.py hl[10] *}
+
+Якщо клієнт спробує надіслати **зайві** дані у **query параметрах**, він отримає **помилку**.
+
+Наприклад, якщо клієнт спробує надіслати query параметр `tool` зі значенням `plumbus`, як у цьому запиті:
+
+```http
+https://example.com/items/?limit=10&tool=plumbus
+```
+
+Він отримає відповідь з **помилкою**, яка повідомить, що query параметр `tool ` не дозволено:
+
+```json
+{
+    "detail": [
+        {
+            "type": "extra_forbidden",
+            "loc": ["query", "tool"],
+            "msg": "Extra inputs are not permitted",
+            "input": "plumbus"
+        }
+    ]
+}
+```
+
+## Підсумок
+
+Ви можете використовувати **Pydantic-моделі** для оголошення **query параметрів** у **FastAPI**. 😎
+
+/// tip | Підказка
+
+Спойлер: Ви також можете використовувати Pydantic-моделі для оголошення cookie та заголовків, але про це Ви дізнаєтеся пізніше в цьому посібнику. 🤫
+
+///

all good, approved!

```suggestion
Ви можете використати конфігурацію моделі Pydantic, щоб заборонити (`forbid`) будь-які зайві (`extra`) поля:
```

[USER_REDACTED] дякую :)


✨ Add OpenAPI `external_docs` parameter to `FastAPI`

This PR adds an external_docs parameter to the __init__ method of the FastAPI class. Currently, the external_docs parameter is not being passed when generating the OpenAPI documentation, which I believe is an oversight. By [NAME_REDACTED] the FastAPI class constructor, the external_docs field will be correctly included in the root of the generated OpenAPI JSON or YAML file.

Reasoning:

The external_docs field provides valuable external documentation links and should be part of the OpenAPI metadata. Including it at the root level of the OpenAPI spec enhances the usability and completeness of the generated documentation, especially for providing references to external resources.

@@ -810,6 +810,32 @@ class Item(BaseModel):
                 """
             ),
         ] = True,
+        openapi_external_docs: Annotated[
+            Optional[Dict[str, Any]],
+            Doc(
+                """
+                This field allows you to provide additional external documentation links.
+                If provided, it must be a dictionary containing:
+
+                * `description`: A brief description of the external documentation.
+                * `url`: The URL pointing to the external documentation. The value **MUST**
+                be a valid URL format.
+
+                **Example**:
+
+                ```python
+                from fastapi import FastAPI
+
+                external_docs = {
+                    "description": "Detailed API Reference",
+                    "url": "https://example.com/api-docs",
+                }
+
+                app = FastAPI(openapi_external_docs=external_docs)
+                ```
+                """
+            ),
+        ] = None,
         **extra: Annotated[
             Any,
             Doc(
@@ -838,6 +864,7 @@ class Item(BaseModel):
         self.swagger_ui_parameters = swagger_ui_parameters
         self.servers = servers or []
         self.separate_input_output_schemas = separate_input_output_schemas
+        self.openapi_external_docs = openapi_external_docs
         self.extra = extra
         self.openapi_version: Annotated[
             str,
@@ -992,6 +1019,7 @@ def openapi(self) -> Dict[str, Any]:
                 tags=self.openapi_tags,
                 servers=self.servers,
                 separate_input_output_schemas=self.separate_input_output_schemas,
+                external_docs=self.openapi_external_docs,
             )
         return self.openapi_schema

@@ -488,6 +488,7 @@ def get_openapi(
     contact: Optional[Dict[str, Union[str, Any]]] = None,
     license_info: Optional[Dict[str, Union[str, Any]]] = None,
     separate_input_output_schemas: bool = True,
+    external_docs: Optional[Dict[str, Any]] = None,
 ) -> Dict[str, Any]:
     info: Dict[str, Any] = {"title": title, "version": version}
     if summary:
@@ -565,4 +566,6 @@ def get_openapi(
         output["webhooks"] = webhook_paths
     if tags:
         output["tags"] = tags
+    if external_docs:
+        output["externalDocs"] = external_docs
     return jsonable_encoder(OpenAPI(**output), by_alias=True, exclude_none=True)  # type: ignore

@@ -3,7 +3,12 @@

 from fastapi import FastAPI, Path, Query

-app = FastAPI()
+external_docs = {
+    "description": "External API documentation.",
+    "url": "https://docs.example.com/api-general",
+}
+
+app = FastAPI(openapi_external_docs=external_docs)

 [USER_REDACTED].api_route("/api_route")

@@ -58,6 +58,10 @@ def test_openapi_schema():
     assert response.json() == {
         "openapi": "3.1.0",
         "info": {"title": "FastAPI", "version": "0.1.0"},
+        "externalDocs": {
+            "description": "External API documentation.",
+            "url": "https://docs.example.com/api-general",
+        },
         "paths": {
             "/api_route": {
                 "get": {

[USER_REDACTED], thanks for your interest and efforts!

I think that description should be simplified.
Please review my suggestions

LGTM

Not sure we need this parameter, but if we want to add it, then this PR effectively does it.

[USER_REDACTED], thanks!

Looks good, thank you! 🚀

```suggestion
                This field allows you to provide additional external documentation links.
                If provided, it must be a dictionary containing:
```

I think we need to make sure it works (CommonMark syntax for rich text formatting)

```suggestion
                from fastapi import FastAPI

                external_docs = {
                    "description": "Detailed API Reference",
                    "url": "https://example.com/api-docs",
                }

                app = FastAPI(openapi_external_docs=external_docs)
```
No need to specify type explicitly here

```suggestion
```
I think we don't need this. The above explanation should be enough

Not sure this is right place for this. Maybe add this to `tests/main.py` and test in `tests/test_application.py`?

[NAME_REDACTED], thanks a lot for your comments—I really appreciate you taking the time to review this!

Could you help me understand a bit more why you think this might not be the right place for the example? It seemed like a good fit to include it here, since the file is already dedicated to OpenAPI scenarios and includes examples that demonstrate the various OpenAPI-related options and attributes.

Hi [USER_REDACTED]! You are welcome!

`test_openapi_examples.py` is intended to test `examples` and `openapi_examples` parameters. Look at tests that are already there and you will see that all of them are about these parameters.

`tests/main.py` includes a lot of different use-cases and `tests/test_application.py` tests openapi schema of app from `tests/main.py`.


✨ Allow array values for OpenAPI schema `type` field

# [NAME_REDACTED] in [NAME_REDACTED] PR addresses an issue with how union types for the "type" field are validated in [NAME_REDACTED] generation. Currently, only a string is allowed for the type definition even though the JSON Schema specification (Section 6.1.1) permits the value to be either a string or an array of unique string values. This discrepancy can lead to confusion or incorrect schema validation when using union types.

Example here that throws pydantic validation error:

```python
# example.py
# uv run --no-project example.py
# /// script
# requires-python = ">=3.12"
# dependencies = [
#     "fastapi",
#     "pydantic",
# ]
# ///

from fastapi.openapi.models import OpenAPI
from pydantic import ValidationError

openapi_dict = {
    "openapi": "3.1.0",
    "info": {"title": "Type Examples with [NAME_REDACTED]", "version": "1.0.0"},
    "components": {
        "schemas": {
            "UnionStringOrNumber": {
                "type": ["string", "number"],
                "description": "An instance is valid if it is either a string or a number.",
                "example": "example as string",
            },
        }
    },
}

def test_openapi_type() -> None:
    try:
        _ = OpenAPI(**openapi_dict)
    except ValidationError as e:
        # Expecting this to raise a ValidationError
        print(f"Validation error: {e}")
        return

    raise NotImplementedError("This should not be reached.")

if __name__ == "__main__":
    test_openapi_type()
    print("Test passed!")
```

Addressed by introducing`TypeValue`, defined as an annotated literal that encompasses [the valid JSON Schema types.](https://json-schema.org/draft/2020-12/json-schema-validation#section-6.1.1)

And updating the Schema model to use the literal values.

After these changes the validation error is removed.

@@ -121,6 +121,12 @@ class ExternalDocumentation(BaseModelWithConfig):
     url: AnyUrl

+# Ref JSON Schema 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation#name-type
+SchemaType = Literal[
+    "array", "boolean", "integer", "null", "number", "object", "string"
+]
+
+
 class Schema(BaseModelWithConfig):
     # Ref: JSON Schema 2020-12: https://json-schema.org/draft/2020-12/json-schema-core.html#name-the-json-schema-core-vocabu
     # [NAME_REDACTED]
@@ -145,7 +151,7 @@ class Schema(BaseModelWithConfig):
     dependentSchemas: Optional[Dict[str, "SchemaOrBool"]] = None
     prefixItems: Optional[List["SchemaOrBool"]] = None
     # TODO: uncomment and remove below when deprecating Pydantic v1
-    # It generales a list of schemas for tuples, before prefixItems was available
+    # It generates a list of schemas for tuples, before prefixItems was available
     # items: Optional["SchemaOrBool"] = None
     items: Optional[Union["SchemaOrBool", List["SchemaOrBool"]]] = None
     contains: Optional["SchemaOrBool"] = None
@@ -157,7 +163,7 @@ class Schema(BaseModelWithConfig):
     unevaluatedProperties: Optional["SchemaOrBool"] = None
     # Ref: JSON [NAME_REDACTED] 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-structural
     # A Vocabulary for [NAME_REDACTED]
-    type: Optional[str] = None
+    type: Optional[Union[SchemaType, List[SchemaType]]] = None
     enum: Optional[List[Any]] = None
     const: Optional[Any] = None
     multipleOf: Optional[float] = Field(default=None, gt=0)

@@ -1,7 +1,13 @@
+from typing import Optional
+
 from fastapi import FastAPI
 from fastapi._compat import PYDANTIC_V2
 from fastapi.testclient import TestClient
 from pydantic import BaseModel
+from typing_extensions import Annotated
+
+if PYDANTIC_V2:
+    from pydantic import WithJsonSchema

 app = FastAPI()

@@ -10,12 +16,17 @@ class Item(BaseModel):
     name: str

     if PYDANTIC_V2:
+        description: Annotated[
+            Optional[str], WithJsonSchema({"type": ["string", "null"]})
+        ] = None
+
         model_config = {
             "json_schema_extra": {
                 "x-something-internal": {"level": 4},
             }
         }
     else:
+        description: Optional[str] = None  # type: ignore[no-redef]

         class Config:
             schema_extra = {
@@ -42,7 +53,11 @@ def foo():
         "name": {
             "title": "Name",
             "type": "string",
-        }
+        },
+        "description": {
+            "title": "Description",
+            "type": ["string", "null"] if PYDANTIC_V2 else "string",
+        },
     },
 }

@@ -57,4 +72,4 @@ def test_response():
     # For coverage
     response = client.get("/foo")
     assert response.status_code == 200, response.text
-    assert response.json() == {"name": "Foo item"}
+    assert response.json() == {"name": "Foo item", "description": None}

@@ -0,0 +1,26 @@
+from typing import List, Optional, Union
+
+import pytest
+from fastapi.openapi.models import Schema, SchemaType
+
+
+[USER_REDACTED].mark.parametrize(
+    "type_value",
+    [
+        "array",
+        ["string", "null"],
+        None,
+    ],
+)
+def test_allowed_schema_type(
+    type_value: Optional[Union[SchemaType, List[SchemaType]]],
+) -> None:
+    """Test that Schema accepts SchemaType, List[SchemaType] and None for type field."""
+    schema = Schema(type=type_value)
+    assert schema.type == type_value
+
+
+def test_invalid_type_value() -> None:
+    """Test that Schema raises ValueError for invalid type values."""
+    with pytest.raises(ValueError, match="2 validation errors for Schema"):
+        Schema(type=True)  # type: ignore[arg-type]

Let's reduce the number of test a bit :)
Just test that all valid types are accepted and maybe add one test for validation error on passing incorrect type

> Added some tests, but the change in this PR is quite small and is only made to allow for type to be a str and list. But since there is no field validator for type its still possible to break the json schema. (by [NAME_REDACTED] a empty list or by [NAME_REDACTED]
> But I think this is a good start and more validation can be added later. wdyt?

I agree, no need to add this validation now.

LGTM

[USER_REDACTED], thanks!

Sounds good, thanks! 🚀

I tweaked it a bit to put the OpenAPI types and related on the same file, it should be ready now. 🍰

```suggestion
def test_schema_type_multiple_type_value() -> None:
    """Test all possible values of TypeValue for Schema type field."""
    schema = Schema(type=TYPE_VALUES)
    assert schema.type == TYPE_VALUES
```

I think now it's too much tests for such small feature. 🤣
Let's just test one combination of all possible values?

```suggestion
def test_invalid_type_value() -> None:
```


🌐 Update Korean translation for `docs/ko/docs/virtual-environments.md`

Hello! 👋 This is my first contribution to [NAME_REDACTED] translation 😊

I translated the `docs/ko/docs/virtual-environments.md` file into Korean.
Since it's a core topic in Python projects, I noticed it hasn't been localized yet, so I decided to help!

I tried to stay as close as possible to the original English format and meaning.

If there is any feedback, I’d be happy to update it right away.

감사합니다!

<img width="1913" alt="image" src="https://github.com/[USER_REDACTED]nts/assets/6de77990-2ad9-4d76-929c-067f67373939" />

Before : https://fastapi.tiangolo.com/ko/virtual-environments/
After.   : https://2d5c56b3.fastapitiangolo.pages.dev/ko/virtual-environments/

@@ -0,0 +1,846 @@
+# 가상 환경
+
+Python 프로젝트를 작업할 때는 **가상 환경** (또는 이와 유사한 도구)을 사용하는 것이 좋습니다. 각 프로젝트 마다 설치하는 패키지를 분리하여 관리할 수 있습니다.
+
+/// info | 정보
+
+이미 가상 환경에 대해 잘 알고 있다면, 이 섹션은 건너 뛰어도 괜찮습니다. 🤓
+
+///
+
+/// tip | 팁
+
+**가상 환경([NAME_REDACTED])** 은 **환경 변수([NAME_REDACTED])** 와 다릅니다.
+
+**환경 변수**는 시스템에 존재하며, 프로그램이 사용할 수 있는 변수입니다.
+
+**가상 환경**은 몇몇 파일로 구성된 하나의 디렉터리입니다.
+
+///
+
+/// info | 정보
+
+이 페이지에서는 **가상 환경**의 사용 방법과 작동 방식을 설명합니다.
+
+만약 **모든 것을 관리해주는 도구** (Python 설치까지 포함)를 사용하고 싶다면 uv를 사용해보세요.
+
+///
+
+## 프로젝트 생성
+
+먼저, 프로젝트를 위한 디렉터리를 하나 생성합니다.
+
+보통 사용자 홈 디렉터리 안에 `code`라는 디렉터리를 만들고, 그 안에 프로젝트마다 하나씩 디렉터리를 만들어 관리합니다.
+
+

+
+```console
+// 홈 디렉터리로 이동
+$ cd
+// 모든 코드 프로젝트를 위한 디렉터리 생성
+$ mkdir code
+// code 디렉터리로 이동
+$ cd code
+// 이번 프로젝트를 위한 디렉터리 생성
+$ mkdir awesome-project
+// 해당 프로젝트 디렉터리로 이동
+$ cd awesome-project
+```
+
+

+
+## 가상 환경 생성
+
+Python 프로젝트를 **처음 시작할 때**, 가상 환경을 **프로젝트 내부**에 생성합니다.
+
+/// tip | 팁
+
+이 작업은 **프로젝트를 처음 설정할 때 한번만** 해주면 됩니다. 이후 작업할 때 반복할 필요는 없습니다.
+
+///
+
+//// tab | `venv`
+
+Python 표준 라이브러리에 포함된 venv 모듈을 사용해 가상 환경을 생성할 수 있습니다.
+
+

+
+```console
+$ python -m venv .venv
+```
+
+

+
+/// details | 명령어 상세 설명
+
+* `python`: `python` 프로그램을 실행합니다.
+* `-m`: 특정 모듈을 스크립트처럼 실행합니다. 대상 모듈을 바로 뒤에 지정합니다.
+* `venv`: Python 표준 라이브러리에 포함된 `venv` 모듈을 실행합니다.
+* `.venv`: 가상 환경을 `.venv` 디렉터리에 생성합니다.
+
+///
+
+////
+
+//// tab | `uv`
+
+`uv`가 설치되어 있다면, uv를 통해 가상 환경을 생성할 수 있습니다.
+
+

+
+```console
+$ uv venv
+```
+
+

+
+/// tip | 팁
+
+`uv`는 기본적으로 `.venv` 디렉터리에 가상 환경을 생성합니다.
+
+별도로 디렉터리 이름을 추가 인자로 넘겨 주면 경로를 지정 할 수 있습니다.
+
+///
+
+////
+
+해당 명령어는 `.venv` 디렉터리에 새로운 가상 환경을 생성합니다.
+
+/// details | `.venv` 또는 다른 이름
+
+가상 환경을 다른 디렉터리에 생성할 수도 있지만, 관례적으로 `.venv` 디렉터리 이름을 사용합니다.
+
+///
+
+## 가상 환경 활성화
+
+이후 실행하는 Python 명령어와 패키지 설치가 가상 환경을 따르도록, 가상 환경을 활성화하세요.
+
+/// tip | 팁
+
+**터미널을 새로 열고** 프로젝트 작업을 시작할 때는, **항상 이 작업을** 해주세요.
+
+///
+
+//// tab | Linux, macOS
+
+

+
+```console
+$ source .venv/bin/activate
+```
+
+

+
+////
+
+//// tab | [NAME_REDACTED]
+
+

+
+```console
+$ .venv\Scripts\Activate.ps1
+```
+
+

+
+////
+
+//// tab | [NAME_REDACTED]
+
+Windows에서 Bash(예: [NAME_REDACTED])를 사용하는 경우:
+
+

+
+```console
+$ source .venv/Scripts/activate
+```
+
+

+
+////
+
+/// tip | 팁
+
+가상 환경에 새로운 패키지를 설치할 때마다, 해당 환경을 다시 활성화하세요.
+
+이렇게 하면 해당 패키지로 설치된 **터미널(CLI) 프로그램**을 사용할 때, 전역에 설치된 다른 버전이 아니라, 가상 환경 안에 설치된 정확한 버전을 사용합니다.
+
+///
+
+## 가상 환경이 활성화 여부 확인
+
+가상 환경이 활성화되었는지 확인합니다. (이전 명령어가 제대로 작동했는지 확인합니다).
+
+/// tip | 팁
+
+이 단계는 **선택 사항**이지만, 모든 것이 예상대로 작동하고 있는지, 그리고 의도한 가상 환경이 활성화 되었는 지 **확인**하는 좋은 방법입니다.
+
+///
+
+//// tab | Linux, macOS, [NAME_REDACTED]
+
+

+
+```console
+$ which python
+
+/home/user/code/awesome-project/.venv/bin/python
+```
+
+

+
+`python` 위치가 프로젝트 내부(이 예시에서는 `awesome-project`)의 `.venv/bin/python` 경로로 표시된다면 성공입니다. 🎉
+
+////
+
+//// tab | [NAME_REDACTED]
+
+

+
+```console
+$ Get-Command python
+
+C:\Users\user\code\awesome-project\.venv\Scripts\python
+```
+
+

+
+`python` 위치가 프로젝트 내부(이 예시에서는 `awesome-project`)의 `.venv\bin\python` 경로로 표시된다면 성공입니다. 🎉
+
+////
+
+## pip 업그레이드
+
+/// tip | 팁
+
+`uv`를 사용한다면, `pip` 대신 `uv`로 패키지를 설치하게 되므로 `pip`을 업그레이드할 필요가 없습니다. 😎
+
+///
+
+`pip`을 사용하여 패키지를 설치하는 경우 (Python 표준 라이브러리에 포함되어 있습니다), **최신 버전으로 업그레이드**하는 것이 좋습니다.
+
+패키지 설치 중 발생하는 다양하고 특이한 에러들은 `pip` 업그레이드로 쉽게 해결되는 경우가 많습니다.
+
+/// tip | 팁
+
+이 작업은 보통 가상 환경을 생성한 **직후 한 번만** 하면 됩니다.
+
+///
+
+가상 환경이 활성화된 상태인지 확인한 후(앞서 설명한 명령어 사용), 아래 명령어를 실행하세요:
+
+

+
+```console
+$ python -m pip install --upgrade pip
+
+---> 100%
+```
+
+

+
+## `.gitignore` 추가하기
+
+**Git**을 사용하고 있다면 (사용하는 것이 좋습니다), `.gitignore` 파일을 추가해서 `.venv` 디렉터리 전체를 Git에서 제외하세요.
+
+/// tip | 팁
+
+`uv`를 사용해 가상 환경을 생성했다면, 이미 이 작업이 자동으로 처리되어 있으므로 이 단계는 건너뛰어도 됩니다. 😎
+
+///
+
+/// tip | 팁
+
+이 작업도 마찬가지로, 가상 환경을 생성한 **직후 한 번만** 하면 됩니다.
+
+///
+
+

+
+```console
+$ echo "*" > .venv/.gitignore
+```
+
+

+
+/// details | 명령어 상세 설명
+
+* `echo "*"`: 터미널에 `*` 텍스트를 "출력"합니다 (다음 설명에서 조금 바뀝니다)
+* `>`: 왼쪽 명령어의 출력 내용을 터미널에 출력하지 않고, 오른쪽에 지정된 파일로 **기록(write)** 하라는 의미입니다.
+* `.gitignore`: 출력된 텍스트가 기록될 파일 이름입니다.
+
+그리고 Git에서 `*`는 "모든 것"을 의미합니다. 따라서 `.venv` 디렉터리 안의 모든 것을 무시하게 됩니다.
+
+이 명령어는 다음과 같은 내용을 가진 `.gitignore` 파일을 생성합니다:
+
+
+```gitignore
+*
+```
+
+///
+
+## 패키지 설치
+
+가상 환경을 활성화한 후, 그 안에 필요한 패키지들을 설치할 수 있습니다.
+
+/// tip | 팁
+
+프로젝트에서 필요한 패키지를 설치하거나 업그레이드할 때는 이 작업을 **한 번만** 하면 됩니다.
+
+만약 특정 패키지의 버전을 업그레이드하거나, 새로운 패키지를 추가할 필요가 생기면 **다시 이 작업을 반복**하면 됩니다.
+
+///
+
+### 패키지 직접 설치
+
+급하게 작업하거나, 프로젝트에 필요한 패키지 목록을 따로 파일로 관리하고 싶지 않은 경우, 패키지를 직접 설치할 수도 있습니다.
+
+/// tip | 팁
+
+패키지 이름과 버전 정보를 파일에 정리해두는 것(예: `requirements.txt` 또는 `pyproject.toml`)은 (매우) 좋은 생각입니다.
+
+///
+
+//// tab | `pip`
+
+

+
+```console
+$ pip install "fastapi[standard]"
+
+---> 100%
+```
+
+

+
+////
+
+//// tab | `uv`
+
+`uv`를 사용하는 경우:
+
+

+
+```console
+$ uv pip install "fastapi[standard]"
+---> 100%
+```
+
+

+
+////
+
+### `requirements.txt`에서 설치
+
+`requirements.txt` 파일이 있다면, 그 안에 명시된 패키지들을 한 번에 설치할 수 있습니다.
+
+//// tab | `pip`
+
+

+
+```console
+$ pip install -r requirements.txt
+---> 100%
+```
+
+

+
+////
+
+//// tab | `uv`
+
+`uv`를 사용하는 경우:
+
+

+
+```console
+$ uv pip install -r requirements.txt
+---> 100%
+```
+
+

+
+////
+
+/// details | `requirements.txt`
+
+다음은 몇 가지 패키지를 포함한 `requirements.txt`의 예시입니다:
+
+```requirements.txt
+fastapi[standard]==0.113.0
+pydantic==2.8.0
+```
+
+///
+
+## 프로그램 실행
+
+가상 환경을 활성화한 후에는 프로그램을 실행할 수 있습니다. 이때 해당 가상 환경에 설치된 Python과 패키지들이 사용됩니다.
+
+

+
+```console
+$ python main.py
+
+[NAME_REDACTED]
+```
+
+

+
+## 에디터 설정
+
+에디터를 사용할 경우, 앞서 만든 가상 환경을 사용하도록 설정하는 것이 좋습니다. (대부분의 에디터는 자동으로 감지하기도 합니다.)
+이렇게 하면 자동 완성 기능이나 코드 내 오류 표시 기능을 제대로 사용할 수 있습니다.
+
+예시:
+
+* VS Code
+* PyCharm
+
+/// tip | 팁
+
+이 설정은 보통 가상 환경을 **처음 만들었을 때 한 번만** 해주면 됩니다.
+
+///
+
+## 가상 환경 비활성화
+
+프로젝트 작업이 끝났다면, 가상 환경을 **비활성화**할 수 있습니다.
+
+

+
+```console
+$ deactivate
+```
+
+

+
+이렇게 하면 이후에 `python` 명령어를 실행했을 때, 가상 환경의 Python이나 그 안에 설치된 패키지들을 사용하지 않게 됩니다.
+
+## 이제 작업할 준비가 되었습니다
+
+이제 프로젝트 작업을 시작할 준비가 완료되었습니다.
+
+
+/// tip | 팁
+
+위 내용을 더 깊이 이해하고 싶으신가요?
+
+그렇다면 계속 읽어 주세요. 👇🤓
+
+///
+
+## 가상 환경을 왜 사용하는가
+
+FastAPI를 사용하려면 먼저 Python을 설치해야 합니다.
+
+그 후에는 FastAPI와 함께 사용할 **기타 패키지들**을 **설치**해야 합니다.
+
+패키지를 설치할 때 보통 Python에 기본 포함된 `pip` 명령어(또는 유사한 도구)를 사용합니다.
+
+하지만 `pip`을 그냥 직접 사용하면, 해당 패키지들은 **전역 Python 환경**(시스템 전체에 설치된 Python)에 설치됩니다.
+
+### 문제점
+
+그렇다면, 전역 Python 환경에 패키지를 설치하면 어떤 문제가 발생할까요?
+
+어느 시점이 되면, **서로 다른 패키지들**에 의존하는 여러 개의 프로그램을 작성하게 될 것입니다. 그리고 이들 중 일부는 **같은 패키지의 서로 다른 버전**을 필요로 할 수 있습니다. 😱
+
+예를 들어, `마법사의 돌(philosophers-stone)` 프로젝트를 만들었다고 가정해봅시다. 이 프로그램은 `해리 포터(harry)`라는 패키지의 `v1` 버전을 **의존**합니다. 따라서 `harry`를 설치해야 합니다.
+
+```mermaid
+flowchart LR
+    stone(philosophers-stone) -->|requires| harry-1[harry v1]
+```
+
+그런데 나중에 `아즈카반의 죄수(prisoner-of-azkaban)`이라는 또 다른 프로젝트를 만들게 되었고, 이 프로젝트도 역시 `harry` 패키지를 사용합니다. 그런데 이 프로젝트는 `harry`의 `v3` 버전이 필요합니다.
+
+```mermaid
+flowchart LR
+    azkaban(prisoner-of-azkaban) --> |requires| harry-3[harry v3]
+```
+
+하지만 이제 문제가 생깁니다. 로컬 가상 환경 대신에 전역 환경에 패키지를 설치하게 되면, 어떤 버전의 `harry`를 설치할지를 선택해야 하기 때문입니다.
+
+예를 들어, `마법사의 돌(philosophers-stone)`을 실행하고 싶다면 먼저 `harry` `v1` 버전을 다음과 같이 설치 해야 합니다:
+
+

+
+```console
+$ pip install "harry==1"
+```
+
+

+
+그러면 결국 전역 Python 환경에는 `harry` `v1`버전이 설치된 상태가 됩니다.
+
+```mermaid
+flowchart LR
+    subgraph global[global env]
+        harry-1[harry v1]
+    end
+    subgraph stone-project[philosophers-stone project]
+        stone(philosophers-stone) -->|requires| harry-1
+    end
+```
+
+하지만 이제 `아즈카반의 죄수(prisoner-of-azkaban)`을 실행하고 싶다면, `harry` `v1`버전을 제거하고 `harry` `v3`버전을 설치해야 합니다. (또는 단순히 `v3`버전을 설치하는 것만으로도 기존의 `v1`버전이 자동으로 제거됩니다.)
+
+

+
+```console
+$ pip install "harry==3"
+```
+
+

+
+그렇게 하면 이제 전역 Python 환경에는 `harry` `v3`버전이 설치된 상태가 됩니다.
+
+그리고 다시 `마법사의 돌(philosophers-stone)`을 실행하려고 하면, **작동하지** 않을 수 있습니다. 왜냐하면 이 프로그램은 `harry` `v1`버전을 필요로 하기 때문입니다.
+
+```mermaid
+flowchart LR
+    subgraph global[global env]
+        harry-1[<strike>harry v1</strike>]
+        style harry-1 fill:#ccc,stroke-dasharray: 5 5
+        harry-3[harry v3]
+    end
+    subgraph stone-project[philosophers-stone project]
+        stone(philosophers-stone) -.-x|⛔| harry-1
+    end
+    subgraph azkaban-project[prisoner-of-azkaban project]
+        azkaban(prisoner-of-azkaban) --> |requires| harry-3
+    end
+```
+
+/// tip | 팁
+
+Python 패키지들은 **새 버전**에서 **호환성 문제(breaking changes)**가 발생하지 않도록 최대한 노력하는 것이 일반적입니다. 하지만 그래도 안전하게 작업하려면, 테스트를 실행해보면서 새 버전을 의도적으로 설치하는 것이 좋습니다.
+
+///
+
+이제, 이런 일이 여러분의 **모든 프로젝트**가 사용하는 **수많은 패키지들**에서 동시에 발생한다고 상상해보세요. 이는 매우 관리하기 어려우며, 결국 **서로 호환되지 않는 버전**의 패키지로 프로젝트를 실행하게 될 가능성이 높고, 그로 인해 어떤 문제가 왜 발생하는지 알 수 없게 될 수 있습니다.
+
+또한 사용하는 운영체제(Linux, Windows, macOS 등)에 따라 Python이 **미리 설치되어 있을 수도** 있습니다. 이런 경우에는 운영체제의 동작에 필요한 특정 버전의 패키지들이 함께 설치되어 있을 수 있습니다. 이 상태에서 전역 Python 환경에 임의의 패키지를 설치하면, 운영체제에 포함된 프로그램 일부가 **깨질 위험**도 있습니다.
+
+## 패키지들은 어디에 설치되는가
+
+Python을 설치하면, 컴퓨터에 여러 디렉터리와 파일들이 생성됩니다.
+
+이 중 일부 디렉터리는 사용자가 설치한 패키지들을 보관하는 역할을 합니다.
+
+예를 들어, 아래 명령어를 실행하면:
+
+

+
+```console
+// 지금 실행하지 않아도 됩니다, 그냥 예제일 뿐이에요 🤓
+$ pip install "fastapi[standard]"
+---> 100%
+```
+
+

+
+해당 명령어는 FastAPI 코드를 포함한 압축 파일을 다운로드합니다. 이 파일은 보통 PyPI에서 받아옵니다.
+
+또한 FastAPI가 의존하는 다른 패키지들도 함께 **다운로드**됩니다.
+
+그리고 그 모든 파일들을 **압축 해제**한 뒤, 컴퓨터의 특정 디렉터리에 저장합니다.
+
+기본적으로 이 파일들은 Python이 설치된 디렉터리 안, 즉 **전역 환경**에 내의 디렉터리에 저장됩니다.
+
+## 가상 환경이란
+
+전역 환경에 모든 패키지를 설치하면서 발생하는 문제에 대한 해결책은, 작업하는 **각 프로젝트마다 가상 환경**을 사용하는 것입니다.
+
+가상 환경은 전역 환경과 매우 유사한 하나의 **디렉터리**이며, 그 안에 해당 프로젝트를 위한 패키지들을 설치할 수 있습니다.
+
+이렇게 하면 각 프로젝트는 자체적인 가상 환경(`.venv` 디렉터리)을 가지게 되며, 그 안에 해당 프로젝트 전용 패키지들을 보유하게 됩니다.
+
+
+```mermaid
+flowchart TB
+    subgraph stone-project[philosophers-stone project]
+        stone(philosophers-stone) --->|requires| harry-1
+        subgraph venv1[.venv]
+            harry-1[harry v1]
+        end
+    end
+    subgraph azkaban-project[prisoner-of-azkaban project]
+        azkaban(prisoner-of-azkaban) --->|requires| harry-3
+        subgraph venv2[.venv]
+            harry-3[harry v3]
+        end
+    end
+    stone-project ~~~ azkaban-project
+```
+
+## 가상 환경 활성화 의미
+
+가상 환경을 활성화한다는 것은, 예를 들어 다음과 같은 명령어를 실행하는 것을 의미합니다:
+
+//// tab | Linux, macOS
+
+

+
+```console
+$ source .venv/bin/activate
+```
+
+

+
+////
+
+//// tab | [NAME_REDACTED]
+
+

+
+```console
+$ .venv\Scripts\Activate.ps1
+```
+
+

+
+////
+
+//// tab | [NAME_REDACTED]
+
+Windows에서 Bash(예: [NAME_REDACTED])를 사용하는 경우:
+
+

+
+```console
+$ source .venv/Scripts/activate
+```
+
+

+
+////
+
+이 명령어는 이후에 실행될 명령어에서 사용될 [환경 변수](environment-variables.md){.internal-link target=_blank} 몇 개를 생성하거나 수정합니다.
+
+이 변수들 중 하나가 바로 `PATH` 변수입니다.
+
+/// tip | 팁
+
+`PATH` 환경 변수에 대해 더 알고 싶다면 [환경 변수 문서의 PATH 환경 변수 섹션](environment-variables.md#path-environment-variable){.internal-link target=_blank}을 참고하세요.
+
+///
+
+가상 환경을 활성화하면, 가상 환경의 경로인 `.venv/bin` (Linux와 macOS) 또는 `.venv\Scripts`(Windows)를 `PATH` 환경 변수에 추가됩니다.
+
+예를 들어, 가상 환경을 활성화하기 전의 `PATH` 변수는 다음과 같았다고 가정해봅시다:
+
+//// tab | Linux, macOS
+
+```plaintext
+/usr/bin:/bin:/usr/sbin:/sbin
+```
+
+시스템은 다음 경로들에서 프로그램을 찾게 됩니다:
+
+* `/usr/bin`
+* `/bin`
+* `/usr/sbin`
+* `/sbin`
+
+////
+
+//// tab | Windows
+
+```plaintext
+C:\Windows\System32
+```
+
+시스템은 다음 경로들에서 프로그램을 찾게 됩니다:
+
+* `C:\Windows\System32`
+
+////
+
+가상 환경을 활성화한 후에는, `PATH` 변수는 다음과 같은 형태가 됩니다:
+
+//// tab | Linux, macOS
+
+```plaintext
+/home/user/code/awesome-project/.venv/bin:/usr/bin:/bin:/usr/sbin:/sbin
+```
+
+시스템은 가장 먼저 다음 경로에서 프로그램을 찾기 시작합니다:
+
+```plaintext
+/home/user/code/awesome-project/.venv/bin
+```
+
+그 후에 다른 디렉터리들을 탐색합니다.
+
+따라서 터미널에 `python`을 입력하면, 시스템은 다음 위치에 있는 Python 프로그램을 찾게 됩니다:
+
+```plaintext
+/home/user/code/awesome-project/.venv/bin/python
+```
+
+그리고 해당 Python을 사용하게 됩니다.
+
+////
+
+//// tab | Windows
+
+```plaintext
+C:\Users\user\code\awesome-project\.venv\Scripts;C:\Windows\System32
+```
+
+시스템은 가장 먼저 다음 경로에서 프로그램을 찾기 시작합니다:
+
+```plaintext
+C:\Users\user\code\awesome-project\.venv\Scripts
+```
+
+그 후에 다른 디렉터리들을 탐색합니다.
+
+따라서 터미널에 `python`을 입력하면, 시스템은 다음 경로에 있는 Python 프로그램을 찾게 됩니다:
+
+```plaintext
+C:\Users\user\code\awesome-project\.venv\Scripts\python
+```
+
+그리고 해당 Python을 사용하게 됩니다.
+
+////
+
+중요한 세부 사항 중 하나는, 가상 환경의 경로가 `PATH` 변수의 가장 **앞**에 추가된다는 점입니다. 시스템은 사용 가능한 다른 Python들보다 **먼저** 이 경로를 찾습니다. 그래서 터미널에서 `python`을 실행하면, 전역 환경의 Python이 아닌 **가상 환경에 있는** Python이 사용됩니다. (예: 전역 환경에 설치된 `python`이 있더라도 그보다 우선합니다.)
+
+가상 환경을 활성화하면 이 외에도 몇 가지 다른 것들이 변경되지만, 이는 그중에서도 가장 중요한 변화 중 하나입니다.
+
+## 가상 환경 확인하기
+
+가상 환경이 활성화 되었는지 확인하려면, 아래 명령어를 사용할 수 있습니다:
+
+//// tab | Linux, macOS, [NAME_REDACTED]
+
+

+
+```console
+$ which python
+
+/home/user/code/awesome-project/.venv/bin/python
+```
+
+

+
+////
+
+//// tab | [NAME_REDACTED]
+
+

+
+```console
+$ Get-Command python
+
+C:\Users\user\code\awesome-project\.venv\Scripts\python
+```
+
+

+
+////
+
+즉, 현재 사용되는 `python` 프로그램은 **가상 환경 내부에 있는 것**입니다.
+
+Linux와 macOS에서는 `which`, Windows PowerShell에서는 `Get-Command` 명령어를 사용합니다.
+
+이 명령어는 `PATH` 환경 변수에 지정된 경로들을 **순서대로 탐색**하면서 `python`이라는 이름의 프로그램을 찾습니다.
+찾는 즉시, 해당 프로그램의 **경로를 출력**합니다.
+
+중요한 점은 터미널에서 `python`을 실행했을 때, 실제로 실행되는 "`python`"이 어떤 것인지 정확히 알 수 있다는 것입니다.
+
+따라서 현재 올바른 가상 환경에 있는지 확인할 수 있습니다.
+
+/// tip | 팁
+
+하나의 가상 환경을 활성화한 뒤, 해당 Python을 가진 상태에서 **또 다른 프로젝트**로 이동하는 것은 흔히 발생합니다.
+
+하지만 이때 이전 프로젝트의 가상 환경에 있는 **잘못된 Python 실행 파일**을 사용하게 되어 새 프로젝트가 **정상 작동하지 않을 수 있습니다.**
+
+그래서 현재 어떤 `python`이 사용되고 있는지 확인할 수 있는 능력은 매우 유용합니다. 🤓
+
+///
+
+## 가상 환경을 비활성화하는 이유
+
+예를 들어 `마법사의 돌(philosophers-stone)`이라는 프로젝트에서 작업 중이라고 해보겠습니다. 이때 해당 **가상 환경을 활성화**하고, 필요한 패키지를 설치하며 작업을 진행합니다.
+
+그런데 이제는 **다른 프로젝트**인 `아즈카반의 죄수(prisoner-of-azkaban)`을 작업하고 싶어졌습니다.
+
+그래서 그 프로젝트 디렉터리로 이동합니다:
+
+

+
+```console
+$ cd ~/code/prisoner-of-azkaban
+```
+
+

+
+만약 `마법사의 돌(philosophers-stone)`의 가상 환경을 비활성화하지 않았다면, 터미널에서 `python`을 실행할 때 여전히 `마법사의 돌(philosophers-stone)` 가상 환경의 Python을 사용하게 됩니다.
+
+

+
+```console
+$ cd ~/code/prisoner-of-azkaban
+
+$ python main.py
+
+// sirius를 임포트하는 데 실패했습니다. 설치되어 있지 않아요 😱
+Traceback (most recent call last):
+    File "main.py", line 1, in <module>
+        import sirius
+```
+
+

+
+하지만 `마법사의 돌(philosophers-stone)`의 가상 환경을 비활성화한 다음, `아즈카반의 죄수(prisoner-of-azkaban)` 프로젝트의 가상 환경을 활성화하면, 이제 `python` 명령어는 `아즈카반의 죄수(prisoner-of-azkaban)` 가상 환경의 Python을 사용하게 됩니다.
+
+

+
+```console
+$ cd ~/code/prisoner-of-azkaban
+
+// 이전 디렉터리에 있을 필요 없이, 어디서든 가상 환경을 비활성화할 수 있습니다. 다른 프로젝트 디렉터리로 이동한 후에도 괜찮아요 😎
+$ deactivate
+
+// prisoner-of-azkaban/.venv 가상 환경을 활성화합니다 🚀
+$ source .venv/bin/activate
+
+// 이제 python을 실행하면, 이 가상 환경에 설치된 sirius 패키지를 찾게 됩니다 ✨
+$ python main.py
+
+못된 짓을 꾸미고 있음을 엄숙히 맹세합니다.🧙
+ImportError는 이제 없습니다. 🐺
+```
+
+

+
+## 대안들
+
+이 문서는 여러분이 Python 프로젝트를 시작하고, **그 내부에서** 어떻게 돌아가는지 알려주는 간단한 가이드입니다.
+
+가상 환경, 패키지 의존성(Requirements), 프로젝트를 관리하는 방법에는 이 외에도 다양한 **대안**들이 존재합니다.
+
+만약 준비가 되었다면, **프로젝트 전체**, 패키지 의존성, 가상 환경 등을 통합적으로 **관리**할 수 있는 도구를 써보는 것도 좋습니다. 그럴 때 추천하는 도구가 바로 uv입니다.
+
+`uv`는 다양한 기능을 지원합니다:
+
+* 다양한 버전의 **Python 설치**
+* 각 프로젝트 별 **가상 환경 관리**
+* **패키지 설치**
+* 프로젝트의 **의존성과 버전** 관리
+* 설치된 패키지들과 그 버전을 **정확히 고정(lock)**해서,개발 환경과 운영 환경이 완전히 동일하게 작동할 수 있도록 보장
+* 이 외에도 다양한 기능을 지원
+
+## 결론
+
+여기까지 모두 읽고 이해했다면, 이제 많은 개발자들보다 가상 환경을 **훨씬 더 깊이 있게 이해**하게 되셨습니다. 🤓
+
+이런 세부적인 내용을 알고 있으면, 언젠가 복잡해 보이는 문제를 디버깅할 때 분명히 큰 도움이 될 것입니다. 이제는 **이 모든 것들이 내부에서 어떻게 작동하는지** 알고 있기 때문입니다. 😎

텍스트 양이 많아서 일단 여기서 한번 끊을게요!

LGTM!!
i left just 1 request comment for my mistake. 😅
if you modify it, and I'll approve!

잘해주셨습니다~~
제가 실수한 거에 대해서 코멘트 남겨뒀어요! ㅋㅋ ㅠㅠㅠ
그거만 반영해주시면 approve 드릴게요~~ 고생하셨습니다!!!

```markdown
/// warning | 경고
```

위와 같이 전체적으로 `| {한국어}` 주석이 필요할 것으로 보입니다 ㅎㅎ!

그 외에는 잘 번역해 주셔서 감사드립니다 ㅎㅎ
위 사항 수정 후 approve 하겠습니다.

```suggestion
이미 가상 환경에 대해 알고 있다면 이 섹션은 건너 뛰어도 괜찮습니다. 🤓
```
added emoji from origianl docs

```suggestion
**가상 환경**은 몇몇 파일로 구성된 하나의 디렉터리입니다.
```

```suggestion
보통은 사용자 홈 디렉터리 안에 `code`라는 디렉터리를 만듭니다.
그리고 그 안에 프로젝트마다 하나씩 디렉터리를 생성합니다.
```
한국어번역에서는 1인칭, 2인칭은 생략하는 형태로 서술하는 것 같더라구요........... (다른 문서도 그런 느낌입니다. 읽어보시면.... 약간 자아를 없애고 문장을 심플하게 만든느낌.)
2문장을 일부러 1문장으로, 더 길게 합칠 필요는 없어서 원래대로 나눴습니다.

```suggestion
**터미널을 새로 열고** 프로젝트 작업을 시작할 때는, **항상 이 작업을** 해주세요.
```
~마다,~마다가
두번 나와서 좀 어색해보이길래... 문장 추천드립니다.

```suggestion
**패키지 이름과 버전 정보를 파일에 정리해두는 것**(예: `requirements.txt` 또는 `pyproject.toml`)은 **(매우) 좋은 습관**입니다.
```

```suggestion
이렇게 하면 이후에 `python` 명령어를 실행했을 때, 가상 환경의 Python이나 그 안에 설치된 패키지들을 사용하지 않게 됩니다.
```

```suggestion
어느 시점이 되면, **서로 다른 패키지들**에 의존하는 여러 개의 프로그램을 작성하게 될 것입니다. 그리고 이들 중 일부는 **같은 패키지의 서로 다른 버전**을 필요로 할 수 있습니다. 😱
```

## en
![image](https://github.com/[USER_REDACTED]nts/assets/95382d12-e2f9-433a-a4d7-28a38e62d60e)

## ko
![image](https://github.com/[USER_REDACTED]nts/assets/1e460eaa-1cc1-4626-8744-0bff37ef30fa)

flow chart부분 문자 간격이 좀 다르네요? 🤔
원인은 잘모르겠지만..

```suggestion
그런데 나중에 `prisoner-of-azkaban`이라는 또 다른 프로젝트를 만들게 되었고, 이 프로젝트도 `harry` 패키지에 의존합니다. 그런데 이 프로젝트는 `harry`의  버전 `3`이 필요합니다.
```

```suggestion
이제 문제가 발생합니다. (로컬 가상환경 대신)전역 환경에 패키지를 설치하게 되면, 어떤 버전의 harry를 설치할지를 선택해야 하기 때문입니다.
```
원본이랑 괄호 위치가 다르긴한데,
한국인한테는 이게 좀더 강조적으로 읽힐 것 같아서, 바꿔서 제안드려요. (본인이 직접 소리내서 읽어보시고 판단부탁드립니다 )

이 문장은 원문보다 의미가 더 잘 드러난 것 같아요.

+) 근데 이런식으로 고치게되면, 문장이 좀... 의미가 점점 벗어날 때도 있어서, 🤔고민 좀 하셔야할 거 같아요.

```suggestion
That means that the system would look for programs in:
```
여기는 번역이 안되어있네요?

```suggestion
하나의 가상 환경을 활성화한 상태에서 **다른 프로젝트로 이동**하면 문제가 발생할 수 있습니다.
```
여기도 원본 텍스트랑 의미가 좀 달라져서... 코멘트 드려요.
나중에 원문이 바뀌면 따라가기 힘들기때문에.....한번 고민 부탁드립니다.

```suggestion
그런데 이제는 **다른 프로젝트**인 `prisoner-of-azkaban`을 작업하고 싶어졌습니다.
```

```suggestion
이 문서는 여러분이 Python 프로젝트를 시작하고, *그 내부에서* 어떻게 돌아가는지 알려주는 간단한 가이드입니다.
```

```suggestion
* 설치된 패키지들과 그 버전을 **정확히 고정(lock)**해서,
  개발 중인 환경과 **운영 환경**이 완전히 동일하게 작동할 수 있도록 보장합니다
```
이 문장도 한국어 상으로는 완벽하지만 의미를 조금더 생각해보셨으면 좋겠습니다!

```suggestion
이러한 세부적인 내용을 알고 있는 것은, 언젠가 복잡해 보이는 문제를 디버깅할 때 분명히 큰 도움이 될 것입니다. 이제는 **이 모든 것들이 내부에서 어떻게 작동하는지** 알고 있기 때문입니다. 😎
```

[USER_REDACTED] 이 문서에서는 예시를 해리포터 1권 3권 얘기를 예시로 들고있는데요.
혹시 이 문장도 이해 하셨나요? 🐺<- 이 emoji의 의미하구요

찾아보고 이해했는데, 녹여내기가 어렵네요 😂😂😂

원문이 바뀌는걸 생각하지 못했네요. 전체적으로 직역에 가깝게 바꿨습니다!

저도 이거는 사실 번역을 해야할까 고민하긴했어요 context문제라 🤔ㅋㅋㅋㅋ 생각해보고 괜찮은 방법이 생각나면, 더 코멘트 드릴게요 !!

직역에 가깝게 변경했습니다!

혹시 한번에 의미가 오지 않을까봐, 해리포터 부분 번역 추가했습니다.

"정확히 고정(lock) " 이 부분이 어렵네요.. 일단 그대로 두고 밑에 내용은 수정했습니다.

개발 중인 환경과 **운영 환경**이 완전히 동일하게 작동할 수 있도록 보장합니다
-> 개발 환경과 운영 환경이 완전히 동일하게 작동할 수 있도록 보장

머글 입장에서 최선을 다해 노력했습니다..

못된 짓을 꾸미고 있음을 엄숙히 맹세합니다.🧙
ImportError는 이제 없습니다. 🐺

ㅋㅋㅋㅋ 고생하셨어요~ 한국은 영어 문화권이 아니다보니, 설명이 필요했는데 이정도면 충분한 것 같습니다

```suggestion
이 문서는 여러분이 Python 프로젝트를 시작하고, **그 내부에서** 어떻게 돌아가는지 알려주는 간단한 가이드입니다.
```
my mistake..😅
* -> **로 바꿨어요  (italic -> bold)

감사합니다!!
문서가 길어서 지쳤는데, 하나하나 읽고 코멘트 달아주셔서 힘을 얻었습니다.
처음에는 뜻 전달 우선이었는데, 조언 주셔서 읽을 때 매끄러움을 생각할 수 있었어요!

```suggestion
/// tip | 팁
```

수정사항 반영했는데, 바로 approve 주셔서 감사합니다! 😊😊


🌐 Add Russian translation for `docs/ru/docs/tutorial/request-form-models.md`

To ensure consistency across the documentation pages, this page uses some _abbreviations_ and _paraphrases_ in its translation. These minor changes relative to the English version were applied by [NAME_REDACTED] [Модели Query-Параметров](https://fastapi.tiangolo.com/ru/tutorial/query-param-models) page, which is similar in structure, and [Данные формы](https://fastapi.tiangolo.com/ru/tutorial/request-forms) page, which is similar in meaning.

For example, in the second article, the original contains the text «Make sure you create a virtual environment, activate it, and then install it, for example: pip install python-multipart», while the translated version is much shorter: «Например, выполните команду pip install python-multipart.». I adopted the shortened version for ease of reading multiple pages in a row.

@@ -0,0 +1,78 @@
+# Модели форм
+
+Вы можете использовать **Pydantic-модели** для объявления **полей форм** в FastAPI.
+
+/// info | Дополнительная информация
+
+Чтобы использовать формы, сначала установите `python-multipart`.
+
+Убедитесь, что вы создали и активировали [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, а затем установите пакет, например:
+
+```console
+$ pip install python-multipart
+```
+
+///
+
+/// note | Заметка
+
+Этот функционал доступен с версии `0.113.0`. 🤓
+
+///
+
+## Pydantic-модель для формы
+
+Вам просто нужно объявить **Pydantic-модель** с полями, которые вы хотите получить как **поля формы**, а затем объявить параметр как `Form`:
+
+{* ../../docs_src/request_form_models/tutorial001_an_py39.py hl[9:11,15] *}
+
+**FastAPI** **извлечёт** данные для **каждого поля** из **данных формы** в запросе и выдаст вам объявленную Pydantic-модель.
+
+## Проверка сгенерированной документации
+
+Вы можете посмотреть поля формы в графическом интерфейсе Документации по пути `/docs`:
+
+

+
+

+
+## Запрет дополнительных полей формы
+
+В некоторых случаях (не особо часто встречающихся) вам может понадобиться **ограничить** поля формы только теми, которые объявлены в Pydantic-модели. И **запретить** любые **дополнительные** поля.
+
+/// note | Заметка
+
+Этот функционал доступен с версии `0.114.0`. 🤓
+
+///
+
+Вы можете сконфигурировать Pydantic-модель так, чтобы запретить (`forbid`) все дополнительные (`extra`) поля:
+
+{* ../../docs_src/request_form_models/tutorial002_an_py39.py hl[12] *}
+
+Если клиент попробует отправить дополнительные данные, то в ответ он получит **ошибку**.
+
+Например, если клиент попытается отправить поля формы:
+
+* `username`: `Rick`
+* `password`: `[NAME_REDACTED]`
+* `extra`: `Mr. Poopybutthole`
+
+То в ответ он получит **ошибку**, сообщающую ему, что поле `extra` не разрешено:
+
+```json
+{
+    "detail": [
+        {
+            "type": "extra_forbidden",
+            "loc": ["body", "extra"],
+            "msg": "Extra inputs are not permitted",
+            "input": "Mr. Poopybutthole"
+        }
+    ]
+}
+```
+
+## Заключение
+
+Вы можете использовать Pydantic-модели для объявления полей форм в FastAPI. 😎

В оригинале не так, здесь нужно исправлять.

Рекомендую придерживаться единого стиля при именовании заголовков.

Например,

## Проверьте сгенерированную документацию
## Запретите дополнительные поля формы

или

## Проверка сгенерированной документации
## Запрет дополнительных полей формы

В изначальном комментарии к PR было представлено объяснение, почему я так сделал. Исправил, и, когда этот примут, планирую сделать другой PR на аналогичные изменение на странице [Данные формы](https://fastapi.tiangolo.com/ru/tutorial/request-forms) для единообразия Документации. Вообще, странно, что при ревью [тех](https://github.com/fastapi/fastapi/pull/9841) изменений не возникло вопросов к такому отклонению от оригинала.

«virtual environment» перевёл как «виртуальное окружение», несмотря на то, что страница по ссылке называется «виртуальная среда»: первый вариант чаще употребляется в сообществе и более однозначный (по второму варианту Google выдаёт статьи по Python вперемешку со статьями по педагогике и системному администрированию).

Согласен, исправлено.

> Исправил, и, когда этот примут, планирую сделать другой PR на аналогичные изменение на странице [Данные формы](https://fastapi.tiangolo.com/ru/tutorial/request-forms) для единообразия Документации

[USER_REDACTED]: ждать совсем необязательно, можно сразу делать PR с внесением поправок


🐛 Fix support for form values with empty strings interpreted as missing (`None` if that's the default), for compatibility with HTML forms

There was a bug in the validation of form data for optional params with None default value. This is a regression that was introduced by https://github.com/fastapi/fastapi/pull/12134.

## Explaination:
The `_get_multidict_value` uses `None` to represent both the absence a value in the dict, and a default value of `None` for that field. When the values are collected in `_extract_form_body`, they are only added to the values if they are not `None`. Later (#12134), some code was added that attempted to complement the model values with values not explicitely in the model, if values for those fields were not already processed earlier. The issue is that since we didn't add values to the initial value dict if they were none, we attempted to add them again in that second iteration. This generally wasn't an issue, except for the fast that a form field set to the empty string (`""`) is interpreted as the absence of that field. This resulted in `_get_multidict_value` picking the default value, that would happen to be `None`, so it wasn't added to the values dict in the first iteration. In the second iteration, we would find that value in the request params again, but set to `""`, and add that to the initial value dict. Later, validation would choke on that empty string.

The fix is to keep track of already visited field, rather that relying on the content of the values dict to complement the missing field. Such thing was already done in `request_params_to_args` for example.

closes #13533

@@ -902,8 +902,9 @@ async def process_fn(
             value = serialize_sequence_value(field=field, value=results)
         if value is not None:
             values[field.alias] = value
+    field_aliases = {field.alias for field in body_fields}
     for key, value in received_body.items():
-        if key not in values:
+        if key not in field_aliases:
             values[key] = value
     return values

@@ -0,0 +1,35 @@
+from typing import Optional
+
+from fastapi import FastAPI, File, Form
+from starlette.testclient import TestClient
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+[USER_REDACTED].post("/urlencoded")
+async def post_url_encoded(age: Annotated[Optional[int], Form()] = None):
+    return age
+
+
+[USER_REDACTED].post("/multipart")
+async def post_multi_part(
+    age: Annotated[Optional[int], Form()] = None,
+    file: Annotated[Optional[bytes], File()] = None,
+):
+    return {"file": file, "age": age}
+
+
+client = TestClient(app)
+
+
+def test_form_default_url_encoded():
+    response = client.post("/urlencoded", data={"age": ""})
+    assert response.status_code == 200
+    assert response.text == "null"
+
+
+def test_form_default_multi_part():
+    response = client.post("/multipart", data={"age": ""})
+    assert response.status_code == 200
+    assert response.json() == {"file": None, "age": None}

I just ran this patch against my full test suite and I can confirm that it fixed all of my tests that had started failing when upgrading from `fastapi` version `0.111.0` to `0.115.12`.

Nice work, [USER_REDACTED]!

LGTM!

I removed the `post_multi_part_file_first` test as it was unrelated to the current issue and that issue (that the order of `File` and `Form` matters) has already been fixed: https://github.com/fastapi/fastapi/pull/11194

[USER_REDACTED], thanks you for your work and patience!

Sorry for the delay handling this, and thank you for the explanation and fix [USER_REDACTED]! 🙌

Thanks for the help [USER_REDACTED]! 🍰

---

I made a local quick example with an HTML form to confirm that browsers by [NAME_REDACTED] fields even if they are empty, and will send them with an empty string. That convinced me this fix is necessary.

---

This will be available in FastAPI 0.123.2, released in the next few hours. 🎉

This looks odd to me.
If you pass a string to an integer field, I would expect a `4xx` error code.

This is a form POST and empty numeric inputs would be submitted as empty strings. IMO it's reasonable that they are accepted and treated like *no value* (`None` / `null`) when the field is an optional integer.


🌐 Add Russian translation for `docs/ru/docs/tutorial/header-param-models.md`

Translate the page `[NAME_REDACTED]` to Russian. Took inspiration from translated `Модели Query-Параметров` :)

@@ -0,0 +1,72 @@
+# Модели Header-параметров
+
+Если у вас есть группа связанных **header-параметров**, то вы можете объединить их в одну **Pydantic-модель**.
+
+Это позволит вам **переиспользовать модель** в **разных местах**, а также задать валидацию и метаданные сразу для всех параметров. 😎
+
+/// note | Заметка
+
+Этот функционал доступен в FastAPI начиная с версии `0.115.0`. 🤓
+
+///
+
+## Header-параметры в виде Pydantic-модели
+
+Объявите нужные **header-параметры** в **Pydantic-модели** и затем аннотируйте параметр как `Header`:
+
+{* ../../docs_src/header_param_models/tutorial001_an_py310.py hl[9:14,18] *}
+
+**FastAPI** **извлечёт** данные для **каждого поля** из **заголовков** запроса и выдаст заданную вами Pydantic-модель.
+
+## Проверьте документацию
+
+Вы можете посмотреть нужные header-параметры в графическом интерфейсе сгенерированной документации по пути `/docs`:
+
+

+
+

+
+## Как запретить дополнительные заголовки
+
+В некоторых случаях (не особо часто встречающихся) вам может понадобиться **ограничить** заголовки, которые вы хотите получать.
+
+Вы можете использовать возможности конфигурации Pydantic-модели для того, чтобы запретить (`forbid`) любые дополнительные (`extra`) поля:
+
+{* ../../docs_src/header_param_models/tutorial002_an_py310.py hl[10] *}
+
+Если клиент попробует отправить **дополнительные заголовки**, то в ответ он получит **ошибку**.
+
+Например, если клиент попытается отправить заголовок `tool` со значением `plumbus`, то в ответ он получит ошибку, сообщающую ему, что header-параметр `tool` не разрешен:
+
+```json
+{
+    "detail": [
+        {
+            "type": "extra_forbidden",
+            "loc": ["header", "tool"],
+            "msg": "Extra inputs are not permitted",
+            "input": "plumbus",
+        }
+    ]
+}
+```
+
+## Как отключить автоматическое преобразование подчеркиваний
+
+Как и в случае с обычными заголовками, если у вас в именах параметров имеются символы подчеркивания, они **автоматически преобразовываются в дефис**.
+
+Например, если в коде есть header-параметр `save_data`, то ожидаемый HTTP-заголовок будет `save-data` и именно так он будет отображаться в документации.
+
+Если по каким-то причинам вам нужно отключить данное автоматическое преобразование, это можно сделать и для Pydantic-моделей для header-параметров.
+
+{* ../../docs_src/header_param_models/tutorial003_an_py310.py hl[19] *}
+
+/// warning | Внимание
+
+Перед тем как устанавливать для параметра `convert_underscores` значение `False`, имейте в виду, что некоторые HTTP-прокси и серверы не разрешают использовать заголовки с символами подчеркивания.
+
+///
+
+## Резюме
+
+Вы можете использовать **Pydantic-модели** для объявления **header-параметров** в **FastAPI**. 😎

```suggestion
Это позволит вам **переиспользовать модель** в **разных местах**, а также установить валидаторы и метаданные сразу для всех параметров. 😎
```

```suggestion
**FastAPI** **извлечёт** данные для **каждого поля** из **заголовков** запроса и выдаст заданную вами Pydantic-модель.
```

```suggestion
## Проверьте документацию
```

```suggestion
## Запретите дополнительные заголовки
```

## Проверка документации

## Запрет дополнительных заголовков

Перед тем как устанавливать для параметра `convert_underscores` значение `False`, имейте в виду, что некоторые HTTP-прокси и серверы запрещают использование заголовков с символами подчеркивания.

Словосочетание "**в некоторых случаях**" уже несет в себе смысл "не часто встречающиеся случаи". Поэтому дальнейшее уточнение "**(не особо часто встречающихся)**" выглядит излишним.
Предлагаю убрать это уточнение, несмотря на то, что в оригинале оно присутствует.

С этим согласен

С этим согласен

С этой правкой я бы не согласился. Во-первых, в оригинале "check the docs" - это явно глагол, то есть "проверьте". Во-вторых, "проверка" - это для русского языка эквивалент "тестирование", "обследование", а в данном случае в оригинале другой смысл, который сводится к тому, что "пойдите посмотрите сгенерированную документацию, там отобразилась моделька, которую вы сделали" :smile:  Поэтому я бы оставил "проверьте".

Опять же, я считаю ,что здесь нужен глагол, потому что по стилю документации и по смыслу мы "**создаем** модель", "**смотрим** доки", "**отключаем** преобразование в дефисы и т.д. Поэтому предлагаю оставить "Запретите".

Здесь я бы не согласился с правкой. "Запрещать" - это prohibit, forbid. А disallow - это скорее "отвергать", "отклонять", "не разрешать", что как раз согласуется с поведением серверов. Сервера не запрещают полученные заголовки, а отвергают в зависимости от настроек :smile:

Ришат, мне не показалось, что уточнение "не особо часто встречающихся" здесь будет излишним. Некоторые случаи здесь - это не частота появления таких случаев, а их особенность. Автор не зря добавил это учтонение, чтобы подчеркнуть, что такая настройка скорее всего вам не нужна. Ну и вроде у нас нет цели сократить текст перевода и сэкономить символы, поэтому я бы оставил, чтобы соответствовать оригиналу :smiley:

Я подумал, что в ру переводе до сих пор не было повелительного наклонения в заголовках. Предложил эту правку исходя из этого соображения. Но оказалось, что заголовки с повелительным наклонением уже присутствуют в переводах.
С вашей аргументацией согласен.

В данном случае, не совсем согласен, т.к. "Запретите дополнительные заголовки" звучит как шаг, который нужно сделать далее. А "Запрет дополнительных заголовков" будет звучать, как шаг, который можно сделать при необходимости.

## Pydantic-модели для заголовков

## Как запретить дополнительные загловки?

## Как отключить автоматическое преобразование подчеркиваний?

[USER_REDACTED] , да, согласен. Как раз оба будут в одинаковом стиле.

[USER_REDACTED] В оригинале именно [NAME_REDACTED], поэтому тут я предлагаю придерживаться почти дословного перевода для точности и оставить "header-параметры".

Кстати, да, звучит как шаг, согласен.
Мне нравится вариант, который предлагает [USER_REDACTED] , но только я бы не ставил вопросительные знаки "Как запретить дополнительные заголовки".


✅ Simplify tests for `settings`

@@ -1,14 +1,26 @@
+import importlib
+
+import pytest
 from fastapi.testclient import TestClient
 from pytest import MonkeyPatch

-from ...utils import needs_pydanticv2
+from ...utils import needs_pydanticv1, needs_pydanticv2

-[USER_REDACTED]_pydanticv2
-def test_settings(monkeypatch: MonkeyPatch):
+[USER_REDACTED].fixture(
+    name="app",
+    params=[
+        pytest.param("tutorial001", marks=needs_pydanticv2),
+        pytest.param("tutorial001_pv1", marks=needs_pydanticv1),
+    ],
+)
+def get_app(request: pytest.FixtureRequest, monkeypatch: MonkeyPatch):
     monkeypatch.setenv("ADMIN_EMAIL", "[EMAIL_REDACTED]")
-    from docs_src.settings.tutorial001 import app
+    mod = importlib.import_module(f"docs_src.settings.{request.param}")
+    return mod.app
+

+def test_settings(app):
     client = TestClient(app)
     response = client.get("/info")
     assert response.status_code == 200, response.text

@@ -1,19 +0,0 @@
-from fastapi.testclient import TestClient
-from pytest import MonkeyPatch
-
-from ...utils import needs_pydanticv1
-
-
-[USER_REDACTED]_pydanticv1
-def test_settings(monkeypatch: MonkeyPatch):
-    monkeypatch.setenv("ADMIN_EMAIL", "[EMAIL_REDACTED]")
-    from docs_src.settings.tutorial001_pv1 import app
-
-    client = TestClient(app)
-    response = client.get("/info")
-    assert response.status_code == 200, response.text
-    assert response.json() == {
-        "app_name": "Awesome API",
-        "admin_email": "[EMAIL_REDACTED]",
-        "items_per_user": 50,
-    }

LGTM

Awesome, thank you! 🚀 ☕

I think that it is better to monkeypatch `get_app()`, instead of test_settings:

```python

[USER_REDACTED].fixture(
    name="app",
    params=[
        pytest.param("tutorial001", marks=needs_pydanticv2),
        pytest.param("tutorial001_pv1", marks=needs_pydanticv1),
    ],
)
def get_app(request: pytest.FixtureRequest, monkeypatch: MonkeyPatch):
    monkeypatch.setenv("ADMIN_EMAIL", "[EMAIL_REDACTED]")
    mod = importlib.import_module(f"docs_src.settings.{request.param}")
    return mod.app

def test_settings(app):
    client = TestClient(app)
    response = client.get("/info")
    assert response.status_code == 200, response.text
    assert response.json() == {
        "app_name": "Awesome API",
        "admin_email": "[EMAIL_REDACTED]",
        "items_per_user": 50,
    }

```

agree, fixed


🌐 Update Korean translation for `docs/ko/docs/advanced/events.md`

Hello!

I encountered that the document of `Lifespan` was not translated in Korean.
So I translated the English document to Korean.

**Korean ver** : https://fastapi.tiangolo.com/ko/advanced/events/   -> To be changed
**English ver**: https://fastapi.tiangolo.com/advanced/events/          -> [NAME_REDACTED] this is my first PR to FastAPI, I would greatly appreciate any feedback 🙇♀

@@ -1,53 +1,165 @@
-# 이벤트: startup과 shutdown
+# Lifespan 이벤트

-필요에 따라 응용 프로그램이 시작되기 전이나 종료될 때 실행되는 이벤트 핸들러(함수)를 정의할 수 있습니다.
+애플리케이션 **시작 전**에 실행되어야 하는 로직(코드)을 정의할 수 있습니다. 이는 이 코드가 **한 번**만 실행되며, **애플리케이션이 요청을 받기 시작하기 전**에 실행된다는 의미입니다.

-이 함수들은 `async def` 또는 평범하게 `def`으로 선언할 수 있습니다.
+마찬가지로, 애플리케이션이 **종료될 때** 실행되어야 하는 로직(코드)을 정의할 수 있습니다. 이 경우, 이 코드는 **한 번**만 실행되며, **여러 요청을 처리한 후**에 실행됩니다.
+
+이 코드가 애플리케이션이 **요청을 받기 시작하기 전에** 실행되고, 요청 처리가 끝난 후 **종료 직전에** 실행되기 때문에 전체 애플리케이션의 **수명(Lifespan)**을 다룹니다. (잠시 후 "수명"이라는 단어가 중요해집니다 😉)
+
+이 방법은 전체 애플리케이션에서 사용해야 하는 **자원**을 설정하거나 요청 간에 **공유되는** 자원을 설정하고, 또는 그 후에 **정리**하는 데 매우 유용할 수 있습니다. 예를 들어, 데이터베이스 연결 풀 또는 공유되는 머신러닝 모델을 로드하는 경우입니다.
+
+
+## 사용 사례
+
+먼저 **사용 사례**를 예로 들어보고, 이를 어떻게 해결할 수 있는지 살펴보겠습니다.
+
+우리가 요청을 처리하기 위해 사용하고 싶은 **머신러닝 모델**이 있다고 상상해 봅시다. 🤖
+
+이 모델들은 요청 간에 공유되므로, 요청마다 모델이 하나씩 있는 것이 아니라, 여러 요청에서 동일한 모델을 사용합니다.
+
+모델을 로드하는 데 **상당한 시간이 걸린다고 상상해 봅시다**, 왜냐하면 모델이 **디스크에서 많은 데이터를 읽어야** 하기 때문입니다. 따라서 모든 요청에 대해 모델을 매번 로드하고 싶지 않습니다.
+
+모듈/파일의 최상위에서 모델을 로드할 수도 있지만, 그러면 **모델을 로드하는데** 시간이 걸리기 때문에, 단순한 자동화된 테스트를 실행할 때도 모델이 로드될 때까지 기다려야 해서 **테스트 속도가 느려집니다**.
+
+이 문제를 해결하려고 하는 것입니다. 요청을 처리하기 전에 모델을 로드하되, 애플리케이션이 요청을 받기 시작하기 직전에만 로드하고, 코드가 로드되는 동안은 로드하지 않도록 하겠습니다.
+
+## Lifespan
+
+`FastAPI` 애플리케이션의 `lifespan` 매개변수와 "컨텍스트 매니저"를 사용하여 *시작*과 *종료* 로직을 정의할 수 있습니다. (컨텍스트 매니저가 무엇인지 잠시 후에 설명드리겠습니다.)
+
+예제를 통해 시작하고, 그 후에 자세히 살펴보겠습니다.
+
+우리는 `yield`를 사용하여 비동기 함수 `lifespan()`을 다음과 같이 생성합니다:
+
+{* ../../docs_src/events/tutorial003.py hl[16,19] *}
+
+여기서 우리는 모델을 로드하는 비싼 *시작* 작업을 시뮬레이션하고 있습니다. `yield` 앞에서 (가짜) 모델 함수를 머신러닝 모델이 담긴 딕셔너리에 넣습니다. 이 코드는 **애플리케이션이 요청을 받기 시작하기 전**, *시작* 동안에 실행됩니다.
+
+그리고 `yield` 직후에는 모델을 언로드합니다. 이 코드는 **애플리케이션이 요청 처리 완료 후**, *종료* 직전에 실행됩니다. 예를 들어, 메모리나 GPU와 같은 자원을 해제하는 작업을 할 수 있습니다.
+
+/// tip | 팁
+
+`shutdown`은 애플리케이션을 **종료**할 때 발생합니다.
+
+새로운 버전을 시작해야 하거나, 그냥 실행을 멈추고 싶을 수도 있습니다. 🤷
+
+///
+
+### Lifespan 함수
+
+먼저 주목할 점은, `yield`를 사용하여 비동기 함수(async function)를 정의하고 있다는 것입니다. 이는 `yield`를 사용한 의존성과 매우 유사합니다.
+
+{* ../../docs_src/events/tutorial003.py hl[14:19] *}
+
+함수의 첫 번째 부분, 즉 `yield` 이전의 코드는 애플리케이션이 시작되기 **전에** 실행됩니다.
+
+그리고 `yield` 이후의 부분은 애플리케이션이 완료된 후 **나중에** 실행됩니다.
+
+### 비동기 컨텍스트 매니저
+
+함수를 확인해보면, `[USER_REDACTED]`로 장식되어 있습니다.
+
+이것은 함수를 "**비동기 컨텍스트 매니저**"라고 불리는 것으로 변환시킵니다.
+
+{* ../../docs_src/events/tutorial003.py hl[1,13] *}
+
+파이썬에서 **컨텍스트 매니저**는 `with` 문에서 사용할 수 있는 것입니다. 예를 들어, `open()`은 컨텍스트 매니저로 사용할 수 있습니다:
+
+```Python
+with open("file.txt") as file:
+    file.read()
+```
+최근 버전의 파이썬에서는 **비동기 컨텍스트 매니저**도 있습니다. 이를 `async with`와 함께 사용합니다:
+
+```Python
+async with lifespan(app):
+    await do_stuff()
+```
+
+컨텍스트 매니저나 위와 같은 비동기 컨텍스트 매니저를 만들면, `with` 블록에 들어가기 전에 `yield` 이전의 코드가 실행되고, `with` 블록을 벗어난 후에는 `yield` 이후의 코드가 실행됩니다.
+
+위의 코드 예제에서는 직접 사용하지 않고, FastAPI에 전달하여 사용하도록 합니다.
+
+`FastAPI` 애플리케이션의 `lifespan` 매개변수는 **비동기 컨텍스트 매니저**를 받기 때문에, 새로운 `lifespan` 비동기 컨텍스트 매니저를 FastAPI에 전달할 수 있습니다.
+
+{* ../../docs_src/events/tutorial003.py hl[22] *}
+
+## 대체 이벤트 (사용 중단)

 /// warning | 경고

-이벤트 핸들러는 주 응용 프로그램에서만 작동합니다. [하위 응용 프로그램 - 마운트](./sub-applications.md){.internal-link target=_blank}에서는 작동하지 않습니다.
+*시작*과 *종료*를 처리하는 권장 방법은 위에서 설명한 대로 `FastAPI` 애플리케이션의 `lifespan` 매개변수를 사용하는 것입니다. `lifespan` 매개변수를 제공하면 `startup`과 `shutdown` 이벤트 핸들러는 더 이상 호출되지 않습니다. `lifespan`을 사용할지, 모든 이벤트를 사용할지 선택해야 하며 둘 다 사용할 수는 없습니다.
+
+이 부분은 건너뛰셔도 좋습니다.

 ///

-## `startup` 이벤트
+*시작*과 *종료* 동안 실행될 이 로직을 정의하는 대체 방법이 있습니다.
+
+애플리케이션이 시작되기 전에 또는 종료될 때 실행해야 하는 이벤트 핸들러(함수)를 정의할 수 있습니다.

-응용 프로그램을 시작하기 전에 실행하려는 함수를 "startup" 이벤트로 선언합니다:
+이 함수들은 `async def` 또는 일반 `def`로 선언할 수 있습니다.
+
+### `startup` 이벤트
+
+애플리케이션이 시작되기 전에 실행되어야 하는 함수를 추가하려면, `"startup"` 이벤트로 선언합니다:

 {* ../../docs_src/events/tutorial001.py hl[8] *}

-이 경우 `startup` 이벤트 핸들러 함수는 단순히 몇 가지 값으로 구성된 `dict` 형식의 "데이터베이스"를 초기화합니다.
+이 경우, `startup` 이벤트 핸들러 함수는 "database"라는 항목(단지 `dict`)을 일부 값으로 초기화합니다.

-하나 이상의 이벤트 핸들러 함수를 추가할 수도 있습니다.
+여러 개의 이벤트 핸들러 함수를 추가할 수 있습니다.

-그리고 응용 프로그램은 모든 `startup` 이벤트 핸들러가 완료될 때까지 요청을 받지 않습니다.
+애플리케이션은 모든 `startup` 이벤트 핸들러가 완료될 때까지 요청을 받기 시작하지 않습니다.

-## `shutdown` 이벤트
+### `shutdown` 이벤트

-응용 프로그램이 종료될 때 실행하려는 함수를 추가하려면 `"shutdown"` 이벤트로 선언합니다:
+애플리케이션이 종료될 때 실행되어야 하는 함수를 추가하려면, `"shutdown"` 이벤트로 선언합니다:

 {* ../../docs_src/events/tutorial002.py hl[6] *}

-이 예제에서 `shutdown` 이벤트 핸들러 함수는 `"Application shutdown"`이라는 텍스트가 적힌 `log.txt` 파일을 추가할 것입니다.
+여기서, `shutdown` 이벤트 핸들러 함수는 `"Application shutdown"`이라는 텍스트를 `log.txt` 파일에 기록합니다.

 /// info | 정보

-`open()` 함수에서 `mode="a"`는 "추가"를 의미합니다. 따라서 이미 존재하는 파일의 내용을 덮어쓰지 않고 새로운 줄을 추가합니다.
+`open()` 함수에서 `mode="a"`는 "추가"를 의미하므로, 파일에 있는 기존 내용은 덮어쓰지 않고 새로운 줄이 추가됩니다.

 ///

 /// tip | 팁

-이 예제에서는 파일과 상호작용 하기 위해 파이썬 표준 함수인 `open()`을 사용하고 있습니다.
+이 경우, 우리는 표준 파이썬 `open()` 함수를 사용하여 파일과 상호작용하고 있습니다.

-따라서 디스크에 데이터를 쓰기 위해 "대기"가 필요한 I/O (입력/출력) 작업을 수행합니다.
+따라서 I/O(입출력) 작업이 포함되어 있어 디스크에 기록되는 것을 "기다리는" 과정이 필요합니다.

-그러나 `open()`은 `async`와 `await`을 사용하지 않기 때문에 이벤트 핸들러 함수는 `async def`가 아닌 표준 `def`로 선언하고 있습니다.
+하지만 `open()`은 `async`와 `await`를 사용하지 않습니다.
+
+그래서 우리는 이벤트 핸들러 함수를 `async def` 대신 일반 `def`로 선언합니다.

 ///

+### `startup`과 `shutdown`을 함께 사용
+
+*시작*과 *종료* 로직이 연결될 가능성이 높습니다. 예를 들어, 무언가를 시작한 후 끝내거나, 자원을 획득한 후 해제하는 등의 작업을 할 수 있습니다.
+
+이러한 작업을 별도의 함수로 처리하면 서로 로직이나 변수를 공유하지 않기 때문에 더 어려워집니다. 값들을 전역 변수에 저장하거나 비슷한 트릭을 사용해야 할 수 있습니다.
+
+그렇기 때문에 위에서 설명한 대로 `lifespan`을 사용하는 것이 권장됩니다.
+
+## 기술적 세부사항
+
+호기심 많은 분들을 위한 기술적인 세부사항입니다. 🤓
+
+ASGI 기술 사양에 따르면, 이는 [NAME_REDACTED]의 일부이며, `startup`과 `shutdown`이라는 이벤트를 정의합니다.
+
 /// info | 정보

-이벤트 핸들러에 관한 내용은 Starlette 이벤트 문서에서 추가로 확인할 수 있습니다.
+Starlette의 `lifespan` 핸들러에 대해 더 읽고 싶다면 Starlette의 Lifespan 문서에서 확인할 수 있습니다.
+
+이 문서에는 코드의 다른 영역에서 사용할 수 있는 lifespan 상태를 처리하는 방법도 포함되어 있습니다.

 ///
+
+## 서브 애플리케이션
+
+🚨 이 lifespan 이벤트(`startup`과 `shutdown`)는 메인 애플리케이션에 대해서만 실행되며, [서브 애플리케이션 - Mounts](sub-applications.md){.internal-link target=_blank}에는 실행되지 않음을 유의하세요.

`///` 블럭이 깨져서 수정 요청 드립니다!

```suggestion
/// tip | 팁
```

```suggestion
/// tip | 팁
```

```suggestion
/// info | 정보
```

```suggestion
/// info | 정보
```

```suggestion
/// warning | 경고
```


🌐 Add Ukrainian translation for `docs/uk/docs/tutorial/metadata.md` page

@@ -0,0 +1,120 @@
+# Метадані та URL-адреси документації
+
+Ви можете налаштувати кілька конфігурацій метаданих у Вашому додатку **FastAPI**.
+
+## Метадані для API
+
+Ви можете встановити такі поля, які використовуються в специфікації OpenAPI та в автоматично згенерованих інтерфейсах документації API:
+
+| Параметр | Тип | Опис |
+|------------|------|-------------|
+| `title` | `str` | Назва API. |
+| `summary` | `str` | Короткий опис API. Доступно з OpenAPI 3.1.0, FastAPI 0.99.0. |
+| `description` | `str` | Більш детальний опис API. Може використовувати Markdown. |
+| `version` | `string` | Версія API. Це версія Вашого додатка, а не OpenAPI. Наприклад, `2.5.0`. |
+| `terms_of_service` | `str` |  URL до умов використання API. Якщо вказано, має бути у форматі URL. |
+| `contact` | `dict` | Інформація для контакту з API. Може містити кілька полів. contact поля

Параметр	Тип	Опис

name	str	Ім'я контактної особи або організації.

url	str	URL з інформацією для контакту. Повинен бути у форматі URL.

email	str	Email контактної особи або організації. Повинен бути у форматі електронної пошти.

 |
+| `license_info` | `dict` | Інформація про ліцензію для API. Може містити кілька полів. license_info поля

Параметр	Тип	Опис

name	str	ОБОВ'ЯЗКОВО (якщо встановлено license_info). Назва ліцензії для API.

identifier	str	Ліцензійний вираз за SPDX для API. Поле identifier взаємовиключне з полем url. Доступно з OpenAPI 3.1.0, FastAPI 0.99.0.

url	str	URL до ліцензії, яка використовується для API. Повинен бути у форматі URL.

 |
+
+Ви можете налаштувати їх наступним чином:
+
+{* ../../docs_src/metadata/tutorial001.py hl[3:16, 19:32] *}
+
+/// tip | Підказка
+
+У полі `description` можна використовувати Markdown, і він буде відображатися у результаті.
+
+///
+
+З цією конфігурацією автоматична документація API виглядатиме так:
+
+
+
+## Ідентифікатор ліцензії
+
+З початку використання OpenAPI 3.1.0 та FastAPI 0.99.0 Ви також можете налаштувати `license_info` за допомогою `identifier` замість `url`.
+
+Наприклад:
+
+{* ../../docs_src/metadata/tutorial001_1.py hl[31] *}
+
+## Метадані для тегів
+
+Ви також можете додати додаткові метадані для різних тегів, які використовуються для групування операцій шляхів, за допомогою параметра `openapi_tags`.
+
+Він приймає список, який містить один словник для кожного тега.
+
+Кожен словник може містити:
+
+* `name` (**обов'язково**): `str` з тією ж назвою тегу, яку Ви використовуєте у параметрі  `tags` у Ваших *операціях шляху* та `APIRouter`s.
+* `description`: `str` з коротким описом тегу. Може містити Markdown і буде відображено в інтерфейсі документації.
+* `externalDocs`: `dict` який описує зовнішню документацію з такими полями:
+    * `description`: `str` з коротким описом зовнішньої документації.
+    * `url` (**обов'язково**): `str`з URL-адресою зовнішньої документації.
+
+### Створення метаданих для тегів
+
+Спробуймо це на прикладі з тегами для `users` та `items`.
+
+Створіть метадані для своїх тегів і передайте їх у параметр  `openapi_tags`:
+
+{* ../../docs_src/metadata/tutorial004.py hl[3:16,18] *}
+
+Зверніть увагу, що в описах можна використовувати Markdown, наприклад, "login" буде показано жирним шрифтом (**login**), а "fancy" буде показано курсивом (_fancy_).
+
+/// tip | Порада
+
+Не обов'язково додавати метадані для всіх тегів, які Ви використовуєте.
+
+///
+
+### Використання тегів
+
+Використовуйте параметр `tags` зі своїми *операціями шляху* (і `APIRouter`) для призначення їх до різних тегів:
+
+{* ../../docs_src/metadata/tutorial004.py hl[21,26] *}
+
+/// info | Інформація
+
+Детальніше про теги читайте в розділі [Конфігурація шляхів операцій](path-operation-configuration.md#tags){.internal-link target=_blank}.
+
+///
+
+### Перевірка документації
+
+Якщо Ви зараз перевірите документацію, вона покаже всі додаткові метадані:
+
+
+
+### Порядок тегів
+
+Порядок кожного словника метаданих тегу також визначає порядок відображення в інтерфейсі документації.
+
+Наприклад, хоча `users` мав би йти після `items` в алфавітному порядку, він відображається перед ними, оскільки ми додали його метадані як перший словник у списку.
+
+## URL для OpenAPI
+
+За замовчуванням схема OpenAPI надається за адресою `/openapi.json`.
+
+Але Ви можете налаштувати це за допомогою параметра `openapi_url`.
+
+Наприклад, щоб налаштувати його на `/api/v1/openapi.json`:
+
+{* ../../docs_src/metadata/tutorial002.py hl[3] *}
+
+Якщо Ви хочете повністю вимкнути схему OpenAPI, Ви можете встановити `openapi_url=None`, це також вимкне інтерфейси документації, які її використовують.
+
+## URL-адреси документації
+
+Ви можете налаштувати два інтерфейси користувача для документації, які включені:
+
+* **Swagger UI**: доступний за адресою `/docs`.
+    * Ви можете змінити його URL за допомогою параметра `docs_url`.
+    * Ви можете вимкнути його, встановивши `docs_url=None`.
+* **ReDoc**: доступний за адресою `/redoc`.
+    * Ви можете змінити його URL за допомогою параметра `redoc_url`.
+    * Ви можете вимкнути його, встановивши `redoc_url=None`.
+
+Наприклад, щоб налаштувати Swagger UI на `/documentation` і вимкнути ReDoc:
+
+{* ../../docs_src/metadata/tutorial003.py hl[3] *}

translation looks good

Тут напевно вже _не короткий опис_. Бо до цього було summary з таким самим призначенням

згоден :) дякую, що підмітив :)


🌐 Add Ukrainian translation for `docs/uk/docs/tutorial/handling-errors.md` page

@@ -0,0 +1,255 @@
+# Обробка Помилок
+
+Є багато ситуацій, коли потрібно повідомити клієнта, який використовує Ваш API, про помилку.
+
+Цим клієнтом може бути браузер із фронтендом, код іншого розробника, IoT-пристрій тощо.
+
+Можливо, Вам потрібно повідомити клієнта, що:
+
+* У нього недостатньо прав для виконання цієї операції.
+* Він не має доступу до цього ресурсу.
+* Елемент, до якого він намагається отримати доступ, не існує.
+* тощо.
+
+У таких випадках зазвичай повертається **HTTP статус-код** в діапазоні **400** (від 400 до 499).
+
+Це схоже на HTTP статус-коди 200 (від 200 до 299). Ці "200" статус-коди означають, що запит пройшов успішно.
+
+Статус-коди в діапазоні 400 означають, що сталася помилка з боку клієнта.
+
+Пам'ятаєте всі ці помилки **404 Not Found** (і жарти про них)?
+
+## Використання `HTTPException`
+
+Щоб повернути HTTP-відповіді з помилками клієнту, використовуйте `HTTPException`.
+
+### Імпорт `HTTPException`
+
+{* ../../docs_src/handling_errors/tutorial001.py hl[1] *}
+
+### Використання `HTTPException` у коді
+
+`HTTPException` — це звичайна помилка Python із додатковими даними, які стосуються API.
+
+Оскільки це помилка Python, Ви не `повертаєте` його, а `генеруєте` (генеруєте помилку).
+
+Це також означає, що якщо Ви перебуваєте всередині допоміжної функції, яку викликаєте всередині своєї *функції операції шляху*, і там генеруєте `HTTPException`, всередині цієї допоміжної функції, то решта коду в *функції операції шляху* не буде виконана. Запит одразу завершиться, і HTTP-помилка з `HTTPException` буде надіслана клієнту.
+
+Перевага використання `генерації` (raise) помилки замість `повернення` значення (return) стане більш очевидним в розділі про Залежності та Безпеку.
+
+У цьому прикладі, якщо клієнт запитує елемент за ID, якого не існує, буде згенеровано помилку зі статус-кодом `404`:
+
+{* ../../docs_src/handling_errors/tutorial001.py hl[11] *}
+
+### Отримана відповідь
+
+Якщо клієнт робить запит за шляхом `http://example.com/items/foo` (де `item_id` `"foo"`), він отримає статус-код 200 і JSON відповідь:
+
+```JSON
+{
+  "item": "The Foo Wrestlers"
+}
+```
+
+Але якщо клієнт робить запит на `http://example.com/items/bar` (де `item_id` має не існуюче значення `"bar"`), то отримає статус-код 404 (помилка "не знайдено") та відповідь:
+
+```JSON
+{
+  "detail": "Item not found"
+}
+```
+
+/// tip | Порада
+
+Під час виклику `HTTPException` Ви можете передати будь-яке значення, яке може бути перетворене в JSON, як параметр `detail`, а не лише рядок (`str`).
+
+Ви можете передати `dict`, `list` тощо.
+
+Вони обробляються автоматично за допомогою **FastAPI** та перетворюються в JSON.
+
+///
+
+## Додавання власних заголовків
+
+Іноді потрібно додати власні заголовки до HTTP-помилки, наприклад, для певних типів безпеки.
+
+Ймовірно, Вам не доведеться використовувати це безпосередньо у своєму коді.
+
+Але якщо Вам знадобиться це для складного сценарію, Ви можете додати власні заголовки:
+
+{* ../../docs_src/handling_errors/tutorial002.py hl[14] *}
+
+## Встановлення власних обробників помилок
+
+Ви можете додати власні обробники помилок за допомогою тих самих утиліт обробки помилок зі Starlette.
+
+Припустимо, у Вас є власний обʼєкт помилки `UnicornException`, яке Ви (або бібліотека, яку Ви використовуєте) може `згенерувати` (`raise`).
+
+І Ви хочете обробляти це виключення глобально за допомогою FastAPI.
+
+Ви можете додати власний обробник виключень за допомогою `[USER_REDACTED].exception_handler()`:
+
+{* ../../docs_src/handling_errors/tutorial003.py hl[5:7,13:18,24] *}
+
+Тут, якщо Ви звернетеся до `/unicorns/yolo`, то згенерується помилка `UnicornException`.
+
+Але вона буде оброблена функцією-обробником `unicorn_exception_handler`.
+
+Отже, Ви отримаєте зрозумілу помилку зі HTTP-статусом `418` і JSON-відповіддю:
+
+```JSON
+{"message": "Oops! yolo did something. There goes a rainbow..."}
+```
+
+/// note | Технічні деталі
+
+Ви також можете використовувати `from starlette.requests import Request` і `from starlette.responses import JSONResponse`.
+
+**FastAPI** надає ті самі `starlette.responses`, що й `fastapi.responses`, просто для зручності розробника. Але більшість доступних відповідей надходять безпосередньо зі Starlette. Те ж саме стосується і `Request`.
+
+///
+
+## Перевизначення обробників помилок за замовчуванням
+
+**FastAPI** має кілька обробників помилок за замовчуванням.
+
+Ці обробники відповідають за повернення стандартних JSON-відповідей, коли Ви `генеруєте` (`raise`) `HTTPException`, а також коли запит містить некоректні дані.
+
+Ви можете перевизначити ці обробники, створивши власні.
+
+### Перевизначення помилок валідації запиту
+
+Коли запит містить некоректні дані, **FastAPI** генерує `RequestValidationError`.
+
+І також включає обробник помилок за замовчуванням для нього.
+
+Щоб перевизначити його, імпортуйте `RequestValidationError` і використовуйте його з `[USER_REDACTED].exception_handler(RequestValidationError)` для декорування обробника помилок.
+
+Обробник помилок отримує `Request` і саму помилку.
+
+{* ../../docs_src/handling_errors/tutorial004.py hl[2,14:16] *}
+
+Тепер, якщо Ви перейдете за посиланням `/items/foo`, замість того, щоб отримати стандартну JSON-помилку:
+
+```JSON
+{
+    "detail": [
+        {
+            "loc": [
+                "path",
+                "item_id"
+            ],
+            "msg": "value is not a valid integer",
+            "type": "type_error.integer"
+        }
+    ]
+}
+```
+
+Ви отримаєте текстову версію:
+
+```
+1 validation error
+path -> item_id
+  value is not a valid integer (type=type_error.integer)
+```
+
+#### `RequestValidationError` проти `ValidationError`
+
+/// warning | Увага
+
+Це технічні деталі, які Ви можете пропустити, якщо вони зараз не важливі для Вас.
+
+///
+
+`RequestValidationError` є підкласом Pydantic `ValidationError`.
+
+**FastAPI** використовує його для того, якщо Ви використовуєте модель Pydantic у `response_model` і у ваших даних є помилка, Ви побачили помилку у своєму журналі.
+
+Але клієнт/користувач не побачить її. Натомість клієнт отримає "[NAME_REDACTED]" зі статусом HTTP `500`.
+
+Так має бути, якщо у Вас виникла `ValidationError` Pydantic у *відповіді* або деінде у вашому коді (не у *запиті* клієнта), це насправді є помилкою у Вашому коді.
+
+І поки Ви її виправляєте, клієнти/користувачі не повинні мати доступу до внутрішньої інформації про помилку, оскільки це може призвести до вразливості безпеки.
+
+### Перевизначення обробника помилок `HTTPException`
+
+Аналогічно, Ви можете перевизначити обробник `HTTPException`.
+
+Наприклад, Ви можете захотіти повернути текстову відповідь замість JSON для цих помилок:
+
+{* ../../docs_src/handling_errors/tutorial004.py hl[3:4,9:11,22] *}
+
+/// note | Технічні деталі
+
+Ви також можете використовувати `from starlette.responses import PlainTextResponse`.
+
+**FastAPI** надає ті самі `starlette.responses`, що й `fastapi.responses`, просто для зручності розробника. Але більшість доступних відповідей надходять безпосередньо зі Starlette.
+
+///
+
+### Використання тіла `RequestValidationError`
+
+`RequestValidationError` містить `body`, який він отримав із некоректними даними.
+
+Ви можете використовувати це під час розробки свого додатка, щоб логувати тіло запиту та налагоджувати його, повертати користувачеві тощо.
+
+{* ../../docs_src/handling_errors/tutorial005.py hl[14] *}
+
+Тепер спробуйте надіслати некоректний елемент, наприклад:
+
+```JSON
+{
+  "title": "towel",
+  "size": "XL"
+}
+```
+Ви отримаєте відповідь, яка повідомить Вам, які саме дані є некоректні у вашому тілі запиту:
+
+
+```JSON hl_lines="12-15"
+{
+  "detail": [
+    {
+      "loc": [
+        "body",
+        "size"
+      ],
+      "msg": "value is not a valid integer",
+      "type": "type_error.integer"
+    }
+  ],
+  "body": {
+    "title": "towel",
+    "size": "XL"
+  }
+}
+```
+
+#### `HTTPException` FastAPI проти `HTTPException` Starlette
+
+**FastAPI** має власний `HTTPException`.
+
+І клас помилки `HTTPException` в **FastAPI** успадковується від класу помилки `HTTPException` в Starlette.
+
+Єдина різниця полягає в тому, що `HTTPException` в **FastAPI** приймає будь-які дані, які можна перетворити на JSON, для поля `detail`, тоді як `HTTPException` у Starlette приймає тільки рядки.
+
+Отже, Ви можете продовжувати використовувати `HTTPException` в **FastAPI** як зазвичай у своєму коді.
+
+Але коли Ви реєструєте обробник виключень, слід реєструвати його для `HTTPException` зі Starlette.
+
+Таким чином, якщо будь-яка частина внутрішнього коду Starlette або розширення чи плагін Starlette згенерує (raise) `HTTPException`, Ваш обробник зможе перехопити та обробити її.
+
+У цьому прикладі, щоб мати можливість використовувати обидва `HTTPException` в одному коді, помилка Starlette перейменовується на `StarletteHTTPException`:
+
+```Python
+from starlette.exceptions import HTTPException as StarletteHTTPException
+```
+
+### Повторне використання обробників помилок **FastAPI**
+
+Якщо Ви хочете використовувати помилки разом із такими ж обробниками помилок за замовчуванням, як у **FastAPI**, Ви можете імпортувати та повторно використовувати їх із `fastapi.exception_handlers`:
+
+{* ../../docs_src/handling_errors/tutorial006.py hl[2:5,15,21] *}
+
+У цьому прикладі Ви просто використовуєте `print` для виведення дуже інформативного повідомлення, але Ви зрозуміли основну ідею. Ви можете обробити помилку та повторно використовувати обробники помилок за замовчуванням.

Great work! as always :) only one little thing

good work

good work, thanks :)

```suggestion
Є багато ситуацій, коли потрібно повідомити клієнта, який використовує Ваш API, про помилку.
```

thank you :)


🌐 Add Russian translation for  `docs/ru/docs/tutorial/middleware.md`

@@ -0,0 +1,74 @@
+# Middleware (Промежуточный слой)
+
+Вы можете добавить промежуточный слой (middleware) в **FastAPI** приложение.
+
+"Middleware" это функция, которая выполняется с каждым запросом до его обработки какой-либо конкретной *операцией пути*.
+А также с каждым ответом перед его возвращением.
+
+
+* Она принимает каждый поступающий **запрос**.
+* Может что-то сделать с этим **запросом** или выполнить любой нужный код.
+* Затем передает **запрос** для последующей обработки (какой-либо *операцией пути*).
+* Получает **ответ** (от *операции пути*).
+* Может что-то сделать с этим **ответом** или выполнить любой нужный код.
+* И возвращает **ответ**.
+
+/// note | Технические детали
+
+Если у вас есть зависимости с `yield`, то код выхода (код после `yield`) будет выполняться *после* middleware.
+
+Если у вас имеются некие фоновые задачи (см. документацию), то они будут запущены после middleware.
+
+///
+
+## Создание middleware
+
+Для создания middleware используйте декоратор `[USER_REDACTED].middleware("http")`.
+
+Функция middleware получает:
+
+* `request` (объект запроса).
+* Функцию `call_next`, которая получает `request` в качестве параметра.
+    * Эта функция передаёт `request` соответствующей *операции пути*.
+    * Затем она возвращает ответ `response`, сгенерированный *операцией пути*.
+* Также имеется возможность видоизменить `response`, перед тем как его вернуть.
+
+{* ../../docs_src/middleware/tutorial001.py hl[8:9,11,14] *}
+
+/// tip | Примечание
+
+Имейте в виду, что можно добавлять свои собственные заголовки при помощи префикса 'X-'.
+
+Если же вы хотите добавить собственные заголовки, которые клиент сможет увидеть в браузере, то вам потребуется добавить их в настройки CORS ([CORS ([NAME_REDACTED])](cors.md){.internal-link target=_blank}), используя параметр `expose_headers`, см. документацию Starlette's CORS docs.
+
+///
+
+/// note | Технические детали
+
+Вы также можете использовать `from starlette.requests import Request`.
+
+**FastAPI** предоставляет такой доступ для удобства разработчиков. Но, на самом деле, это `Request` из Starlette.
+
+///
+
+### До и после `response`
+
+Вы можете добавить код, использующий `request` до передачи его какой-либо *операции пути*.
+
+А также после формирования `response`, до того, как вы его вернёте.
+
+Например, вы можете добавить собственный заголовок `X-Process-Time`, содержащий время в секундах, необходимое для обработки запроса и генерации ответа:
+
+{* ../../docs_src/middleware/tutorial001.py hl[10,12:13] *}
+
+/// tip | Примечание
+
+Мы используем `time.perf_counter()` вместо `time.time()` для обеспечения большей точности наших примеров. 🤓
+
+///
+
+## Другие middleware
+
+О других middleware вы можете узнать больше в разделе [[NAME_REDACTED]: [NAME_REDACTED]](../advanced/middleware.md){.internal-link target=_blank}.
+
+В следующем разделе вы можете прочитать, как настроить CORS с помощью middleware.

Хорошая работа 👍

Может термин "промежуточный слой" лучше подходит?

```suggestion
В следующем разделе вы можете прочитать, как работать с <abbr title="[NAME_REDACTED]">CORS</abbr> middleware.
```

```suggestion
* Также имеется возможность обработать `response` перед тем, как его вернуть.
```

исправила, но с изменением:
`Также имеется возможность обработать `response`, перед тем как его вернуть.`

изменила перевод на более точный (и запятая теперь на месте):

`В следующем разделе вы можете прочитать, как настроить <abbr title="[NAME_REDACTED]">CORS</abbr> с помощью middleware.`

Вы можете добавить промежуточный слой (middleware) в **FastAPI** приложения.

* Затем передает запрос для последующей обработки в *функцию эндпоинта*.

* Получает **ответ** от *функции эндпоинта*.

* И возвращает **ответ**.

Если у вас есть зависимости с `yield`, то код выхода (exit code) будет выполняться *после* middleware.

Если имеются фоновые задачи (см. документацию), то они будут запущены после middleware.

Но если вы хотите, чтобы клиент в браузере смог увидеть ваши собственные заголовки, то потребуется добавить их в настройки CORS ([CORS ([NAME_REDACTED])](cors.md){.internal-link target=_blank}), используя параметр `expose_headers`, см. документацию Starlette's CORS docs.

/// note | Технические детали

**FastAPI** предоставляет такой доступ для удобства разработчиков. На самом деле это `Request` из Starlette.

Вы можете добавить код, использующий `request` до передачи его в *функцию эндпоинта*.

А также после формирования `response`, до того как вы его вернёте.

Например, вы можете добавить собственный заголовок `X-Process-Time`, содержащий необходимое для обработки запроса и генерации ответа время в секундах:

Здесь мы используем <a href="https://docs.python.org/3/library/time.html#time.perf_counter" class="external-link" target="_blank">`time.perf_counter()`</a> вместо `time.time()` для обеспечения большей точности для данных целей. 🤓

Как настроить <abbr title="[NAME_REDACTED]">CORS</abbr> с помощью middleware вы можете прочитать в следующем разделе.

/// tip | Примечание

/// tip | Примечание

здесь оставлю как есть

поставлю запятую :laughing:  (так будет проще для восприятия)

оставлю как есть

оставлю как есть

Да, согласен полностью)

В данном элементе списка в конце стоит точка. У остальных же элементов этого списка точка в конце не стоит.
Нужно привести к единому виду (в оригинале везде точки стоят).

Я же правильно понимаю, что под "exit code" имеется ввиду "участок кода после `yield`"?

Если это так, то может вместо "кода выхода" и "exit code" явно пропишем что имеется ввиду "код после `yield`"?

не знаю, как лучше, но в оригинале используется "path operation", да и в других местах доки это называют операцией пути

Например, можно так:
Если у вас есть зависимости с `yield`, то код выхода (код после `yield`) будет выполняться *после* middleware.

Если же вы хотите добавить собственные заголовки, которые клиент сможет увидеть в браузере, то вам потребуется добавить их в настройки CORS ([CORS ([NAME_REDACTED])](cors.md){.internal-link target=_blank}), используя параметр `expose_headers` (см. документацию Starlette's CORS docs).

давай оставим как есть, это по сути одно и то же

Там косяк с expose_headers` 😅

А "см. что-то что-то" вроде принято в скобках указывать. Не припомню, чтобы через запятую так писали.

можно по-всякому: это авторский выбор
зато без скобок эстетичнее и легче читается

[USER_REDACTED]: имхо, *функция эндпоинта* понятнее и используется чаще.  Но можно и обсудить. :blush:

> "Middleware" это функция, которая выполняется с каждым запросом до его обработки *функцией эндпоинта*.

> "Middleware" это функция, которая выполняется с каждым запросом до его обработки *операцией пути*.

["эндпоинт at fastapi.tiangolo.com"](https://www.google.com/search?q=%22%D1%8D%D0%BD%D0%B4%D0%BF%D0%BE%D0%B8%D0%BD%D1%82%22+site%3Afastapi.tiangolo.com)

Я согласен с [USER_REDACTED].
[USER_REDACTED], ведь в переводе "Большие приложения, в которых много файлов", ты все "_path operation_" перевела либо как "_операция пути_", либо как "_эндпоинт_".
А "_функция операции пути_" или "_функция эндпоинта_" - это уже "_operation path function_".

я исхожу из контекста, в данном контексте нужно переводить operation как функцию (тут проблем нет), а
path как эндпоинт  (это некорректно с точки зрения русского языка)

вообще-то, если дословно, то "path operation" = "функция пути"

Исходя из предыдущих переводов я думал так:
"path operation" = "операция пути" или "эндпоинт"
"path operation function" = "функция операции пути" или "функция эндпоинта"

И под "path operation" подрузумевается вся вот эта конструкция:

[USER_REDACTED].get("/")
async def root():
    return {"message": "[NAME_REDACTED]"}

где `async def root()` является "функцией операции пути" или "функцией эндпоинта"

[USER_REDACTED] , [USER_REDACTED], всё, я вас услышала, теперь понятно, что вы имеете в виду
Hello, World! : или место встречи изменить нельзя :smile:

Исправляю на операцию пути. :smile:

[USER_REDACTED]

endpoint -> url
pass operations -> набор функций/операций, обслуживающих запросы на определенный url
pass operation -> "операция пути" ~ "операция эндпоинта" ~ "функция эндпоинта"
path operation decorator: `[USER_REDACTED].get("/")`
path operation function: `async def root()`

[USER_REDACTED] , здесь тоже надо слово "запрос" жирным выделить
![2025-02-27_20-31](https://github.com/[USER_REDACTED]nts/assets/74806963-98f6-4b78-982a-500cfdd12f0e)

[USER_REDACTED] , разве мы не можем просто следующим образом переводить:
- если в оригинале написано "path operation", то переводим "операция пути"
- если в оригинале написано "path operation function", то переводим "функция операции пути"

?

[USER_REDACTED]
1) можем, но это не всегда работает.
2) операция пути не( самый )удачный термин, и в обычной жизни не используется
3) и хочется объяснить более доступно, но не всегда получается :blush:

[USER_REDACTED] , но ведь мы можем пока придерживаться такой (пусть и не самой удачной) терминологии, просто чтобы везде было однообразно.
И потом если кому-то в голову придет более удачный термин, то тогда массово разом везде поправить.
И если везде будут одинаковые неудачные термины, то массово поправить будет легче, чем если они будут везде по-разному прописаны.

[USER_REDACTED], мы такой стратегии и придерживаемся, разве нет? :smile: