```console
-$ fastapi dev main.py
-INFO Using path main.py
-INFO Resolved absolute path /home/user/code/awesomeapp/main.py
-INFO Searching for package file structure from directories with __init__.py files
-INFO Importing from /home/user/code/awesomeapp
-
- ╭─ Python module file ─╮
- │ │
- │ 🐍 main.py │
- │ │
- ╰──────────────────────╯
-
-INFO Importing module main
-INFO Found importable FastAPI app
-
- ╭─ Importable FastAPI app ─╮
- │ │
- │ from main import app │
- │ │
- ╰──────────────────────────╯
-
-INFO Using import string main:app
-
- ╭────────── FastAPI CLI - Development mode ───────────╮
- │ │
- │ Serving at: http://127.0.0.1:8000 │
- │ │
- │ API docs: http://127.0.0.1:8000/docs │
- │ │
- │ Running in development mode, for production use: │
- │ │
- │ fastapi run │
- │ │
- ╰─────────────────────────────────────────────────────╯
-
-INFO: Will watch for changes in these directories: ['/home/user/code/awesomeapp']
-INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
-INFO: Started reloader process [2265862] using WatchFiles
-INFO: Started server process [2265873]
-INFO: Waiting for application startup.
-INFO: Application startup complete.
+$ fastapi dev main.py
+
+ FastAPI Starting development server 🚀
+
+ Searching for package file structure from directories with
+ __init__.py files
+ Importing from /home/user/code/awesomeapp
+
+ module 🐍 main.py
+
+ code Importing the FastAPI app object from the module with the
+ following code:
+
+ from main import app
+
+ app Using import string: main:app
+
+ server Server started at http://127.0.0.1:8000
+ server Documentation at http://127.0.0.1:8000/docs
+
+ tip Running in development mode, for production use:
+ fastapi run
+
+ Logs:
+
+ INFO Will watch for changes in these directories:
+ ['/home/user/code/awesomeapp']
+ INFO Uvicorn running on http://127.0.0.1:8000(Press CTRL+C to
+ quit)
+ INFO Started reloader process [383138] using WatchFiles
+ INFO Started server process [383153]
+ INFO Waiting for application startup.
+ INFO Application startup complete.
```
-Програма командного рядка `fastapi` це **FastAPI CLI**.
+Програма командного рядка під назвою `fastapi` — це **FastAPI CLI**.
-FastAPI CLI приймає шлях до Вашої Python програми (напр. `main.py`) і автоматично виявляє екземпляр `FastAPI` (зазвичай названий `app`), обирає коректний процес імпорту, а потім обслуговує його.
+FastAPI CLI бере шлях до вашої Python-програми (наприклад, `main.py`) і автоматично виявляє екземпляр `FastAPI` (зазвичай з назвою `app`), визначає правильний процес імпорту, а потім обслуговує його.
-Натомість, для запуску у продакшн використовуйте `fastapi run`. 🚀
+Натомість, для продакшн ви використали б `fastapi run`. 🚀
-Всередині **FastAPI CLI** використовує Uvicorn, високопродуктивний, production-ready, ASGI cервер. 😎
+Внутрішньо **FastAPI CLI** використовує Uvicorn, високопродуктивний, production-ready, ASGI сервер. 😎
-## `fastapi dev`
+## `fastapi dev` { #fastapi-dev }
-Використання `fastapi dev` ініціює режим розробки.
+Запуск `fastapi dev` ініціює режим розробки.
-За замовчуванням, **автоматичне перезавантаження** увімкнене, автоматично перезавантажуючи сервер кожного разу, коли Ви змінюєте Ваш код. Це ресурсо-затратно, та може бути менш стабільним, ніж коли воно вимкнене. Ви повинні використовувати його тільки під час розробки. Воно також слухає IP-адресу `127.0.0.1`, що є IP Вашого девайсу для самостійної комунікації з самим собою (`localhost`).
+За замовчуванням **auto-reload** увімкнено, і сервер автоматично перезавантажується, коли ви вносите зміни у ваш код. Це ресурсоємно та може бути менш стабільним, ніж коли його вимкнено. Вам слід використовувати це лише для розробки. Також він слухає IP-адресу `127.0.0.1`, яка є IP-адресою для того, щоб ваша машина могла взаємодіяти лише сама з собою (`localhost`).
-## `fastapi run`
+## `fastapi run` { #fastapi-run }
-Виконання `fastapi run` запустить FastAPI у продакшн-режимі за замовчуванням.
+Виконання `fastapi run` за замовчуванням запускає FastAPI у продакшн-режимі.
-За замовчуванням, **автоматичне перезавантаження** вимкнене. Воно також прослуховує IP-адресу `0.0.0.0`, що означає всі доступні IP адреси, тим самим даючи змогу будь-кому комунікувати з девайсом. Так Ви зазвичай будете запускати його у продакшн, наприклад у контейнері.
+За замовчуванням **auto-reload** вимкнено. Також він слухає IP-адресу `0.0.0.0`, що означає всі доступні IP-адреси, таким чином він буде публічно доступним для будь-кого, хто може взаємодіяти з машиною. Зазвичай саме так ви запускатимете його в продакшн, наприклад у контейнері.
-В більшості випадків Ви можете (і маєте) мати "termination proxy", який обробляє HTTPS для Вас, це залежить від способу розгортання вашого додатку, Ваш провайдер може зробити це для Вас, або Вам потрібно налаштувати його самостійно.
+У більшості випадків ви (і вам слід) матимете «termination proxy», який обробляє HTTPS для вас зверху; це залежатиме від того, як ви розгортаєте ваш застосунок: ваш провайдер може зробити це за вас, або вам може знадобитися налаштувати це самостійно.
-/// tip
+/// tip | Порада
-Ви можете дізнатись більше про це у [документації про розгортування](deployment/index.md){.internal-link target=_blank}.
+Ви можете дізнатися більше про це в [документації з розгортання](deployment/index.md){.internal-link target=_blank}.
///
diff --git a/docs/uk/docs/features.md b/docs/uk/docs/features.md
index aa0ef7c79..d8233115f 100644
--- a/docs/uk/docs/features.md
+++ b/docs/uk/docs/features.md
@@ -1,21 +1,21 @@
-# Функціональні можливості
+# Функціональні можливості { #features }
-## Функціональні можливості FastAPI
+## Функціональні можливості FastAPI { #fastapi-features }
**FastAPI** надає вам такі можливості:
-### Використання відкритих стандартів
+### На основі відкритих стандартів { #based-on-open-standards }
* OpenAPI для створення API, включаючи оголошення шляхів, операцій, параметрів, тіл запитів, безпеки тощо.
* Автоматична документація моделей даних за допомогою JSON Schema (оскільки OpenAPI базується саме на JSON Schema).
* Розроблено на основі цих стандартів після ретельного аналізу, а не як додатковий рівень поверх основної архітектури.
-* Це також дає змогу автоматично **генерувати код клієнта** багатьма мовами.
+* Це також дає змогу використовувати автоматичну **генерацію клієнтського коду** багатьма мовами.
-### Автоматична генерація документації
+### Автоматична документація { #automatic-docs }
-Інтерактивна документація API та вебінтерфейс для його дослідження. Оскільки фреймворк базується на OpenAPI, є кілька варіантів, два з яких включені за замовчуванням.
+Інтерактивна документація API та вебінтерфейси для його дослідження. Оскільки фреймворк базується на OpenAPI, є кілька варіантів, 2 з яких включені за замовчуванням.
-* Swagger UI — дозволяє інтерактивно переглядати API, викликати та тестувати його прямо у браузері.
+* Swagger UI — з інтерактивним дослідженням, викликом і тестуванням вашого API прямо з браузера.

@@ -23,23 +23,25 @@

-### Тільки сучасний Python
+### Лише сучасний Python { #just-modern-python }
-FastAPI використовує стандартні **типи Python** (завдяки Pydantic). Вам не потрібно вивчати новий синтаксис — лише стандартний сучасний Python.
+Усе базується на стандартних оголошеннях **типів Python** (завдяки Pydantic). Жодного нового синтаксису для вивчення. Лише стандартний сучасний Python.
-Якщо вам потрібне коротке нагадування про використання типів у Python (навіть якщо ви не використовуєте FastAPI), перегляньте короткий підручник: [Вступ до типів Python](python-types.md){.internal-link target=_blank}.
+Якщо вам потрібно 2-хвилинне нагадування про те, як використовувати типи Python (навіть якщо ви не використовуєте FastAPI), перегляньте короткий підручник: [Типи Python](python-types.md){.internal-link target=_blank}.
-Ось приклад стандартного Python-коду з типами:
+Ви пишете стандартний Python з типами:
```Python
from datetime import date
+
from pydantic import BaseModel
-# Оголошення змінної як str
-# з підтримкою автодоповнення у редакторі
+# Оголосіть змінну як str
+# та отримайте підтримку редактора всередині функції
def main(user_id: str):
return user_id
+
# Модель Pydantic
class User(BaseModel):
id: int
@@ -47,7 +49,7 @@ class User(BaseModel):
joined: date
```
-Приклад використання цієї моделі:
+Далі це можна використовувати так:
```Python
my_user: User = User(id=3, name="John Doe", joined="2018-07-19")
@@ -65,19 +67,21 @@ my_second_user: User = User(**second_user_data)
`**second_user_data` означає:
-Передати ключі та значення словника `second_user_data` як аргументи у вигляді "ключ-значення", еквівалентно `User(id=4, name="Mary", joined="2018-11-30")`.
+Передати ключі та значення словника `second_user_data` безпосередньо як аргументи у вигляді «ключ-значення», еквівалентно: `User(id=4, name="Mary", joined="2018-11-30")`
///
-### Підтримка редакторів (IDE)
+### Підтримка редакторів (IDE) { #editor-support }
-Фреймворк спроєктований так, щоб бути легким і інтуїтивно зрозумілим. Усі рішення тестувалися у різних редакторах ще до початку розробки, щоб забезпечити найкращий досвід програмування.
+Увесь фреймворк спроєктовано так, щоб ним було легко та інтуїтивно користуватися; усі рішення тестувалися у кількох редакторах ще до початку розробки, щоб забезпечити найкращий досвід розробки.
-За результатами опитувань розробників Python однією з найпопулярніших функцій є "автодоповнення".
+З опитувань розробників Python зрозуміло що однією з найуживаніших функцій є «автодоповнення».
-**FastAPI** повністю підтримує автодоповнення у всіх місцях, тому вам рідко доведеться повертатися до документації.
+Увесь фреймворк **FastAPI** побудований так, щоб це забезпечити. Автодоповнення працює всюди.
-Приклад автодоповнення у редакторах:
+Вам рідко доведеться повертатися до документації.
+
+Ось як ваш редактор може вам допомогти:
* у Visual Studio Code:
@@ -87,17 +91,25 @@ my_second_user: User = User(**second_user_data)

-### Короткий код
-FastAPI має розумні налаштування **за замовчуванням**, але всі параметри можна налаштовувати відповідно до ваших потреб. Однак за замовчуванням все "просто працює".
+Ви отримаєте автодоповнення в коді, який раніше могли вважати навіть неможливим. Наприклад, для ключа `price` всередині JSON body (який міг бути вкладеним), що надходить із запиту.
+
+Більше не доведеться вводити неправильні назви ключів, постійно повертатися до документації або прокручувати вгору-вниз, щоб знайти, чи ви зрештою використали `username` чи `user_name`.
+
+### Короткий код { #short }
+
+FastAPI має розумні **налаштування за замовчуванням** для всього, з можливістю конфігурації всюди. Усі параметри можна точно налаштувати під ваші потреби та визначити потрібний вам API.
+
+Але за замовчуванням усе **«просто працює»**.
+
+### Валідація { #validation }
-### Валідація
* Підтримка валідації для більшості (або всіх?) **типів даних Python**, зокрема:
* JSON-об'єктів (`dict`).
- * JSON-списків (`list`) з визначенням типів елементів.
- * Рядків (`str`) із мінімальною та максимальною довжиною.
- * Чисел (`int`, `float`) з обмеженнями мінімальних та максимальних значень тощо.
+ * JSON-масивів (`list`) із визначенням типів елементів.
+ * Полів-рядків (`str`) із визначенням мінімальної та максимальної довжини.
+ * Чисел (`int`, `float`) з мінімальними та максимальними значеннями тощо.
-* Валідація складніших типів, таких як:
+* Валідація для більш екзотичних типів, як-от:
* URL.
* Email.
* UUID.
@@ -105,55 +117,55 @@ FastAPI має розумні налаштування **за замовчува
Уся валідація виконується через надійний та перевірений **Pydantic**.
-### Безпека та автентифікація
+### Безпека та автентифікація { #security-and-authentication }
-**FastAPI** підтримує вбудовану автентифікацію та авторизацію, без прив’язки до конкретних баз даних чи моделей даних.
+Інтегровані безпека та автентифікація. Без жодних компромісів із базами даних чи моделями даних.
-Підтримуються всі схеми безпеки OpenAPI, включаючи:
+Підтримуються всі схеми безпеки, визначені в OpenAPI, включно з:
* HTTP Basic.
-* **OAuth2** (також із підтримкою **JWT-токенів**). Див. підручник: [OAuth2 із JWT](tutorial/security/oauth2-jwt.md){.internal-link target=_blank}.
+* **OAuth2** (також із підтримкою **JWT tokens**). Перегляньте підручник: [OAuth2 із JWT](tutorial/security/oauth2-jwt.md){.internal-link target=_blank}.
* Ключі API в:
* Заголовках.
* Параметрах запиту.
* Cookies тощо.
-А також усі можливості безпеки від Starlette (зокрема **сесійні cookies**).
+А також усі можливості безпеки від Starlette (зокрема **session cookies**).
-Усі вони створені як багаторазові інструменти та компоненти, які легко інтегруються з вашими системами, сховищами даних, реляційними та NoSQL базами даних тощо.
+Усе це зроблено як багаторазові інструменти та компоненти, які легко інтегруються з вашими системами, сховищами даних, реляційними та NoSQL базами даних тощо.
-### Впровадження залежностей
+### Впровадження залежностей { #dependency-injection }
-**FastAPI** містить надзвичайно просту у використанні, але потужну систему впровадження залежностей.
+FastAPI містить надзвичайно просту у використанні, але надзвичайно потужну систему Dependency Injection.
-* Залежності можуть мати власні залежності, утворюючи ієрархію або **"граф залежностей"**.
-* Усі залежності автоматично керуються фреймворком.
-* Усі залежності можуть отримувати дані з запитів і розширювати **обмеження операції за шляхом** та автоматичну документацію.
-* **Автоматична валідація** навіть для параметрів *операцій шляху*, визначених у залежностях.
-* Підтримка складних систем автентифікації користувачів, **з'єднань із базами даних** тощо.
-* **Жодних обмежень** щодо використання баз даних, фронтендів тощо, але водночас проста інтеграція з усіма ними.
+* Навіть залежності можуть мати власні залежності, утворюючи ієрархію або **«граф» залежностей**.
+* Усе **автоматично обробляється** фреймворком.
+* Усі залежності можуть вимагати дані із запитів і **розширювати обмеження операції шляху** та автоматичну документацію.
+* **Автоматична валідація** навіть для параметрів *операції шляху*, визначених у залежностях.
+* Підтримка складних систем автентифікації користувачів, **підключень до баз даних** тощо.
+* **Жодних компромісів** із базами даних, фронтендами тощо. Але проста інтеграція з усіма ними.
-### Немає обмежень на "плагіни"
+### Необмежені «плагіни» { #unlimited-plug-ins }
-Або іншими словами, вони не потрібні – просто імпортуйте та використовуйте необхідний код.
+Інакше кажучи, вони не потрібні — імпортуйте та використовуйте код, який вам потрібен.
-Будь-яка інтеграція спроєктована настільки просто (з використанням залежностей), що ви можете створити "плагін" для свого застосунку всього у 2 рядках коду, використовуючи ту саму структуру та синтаксис, що й для ваших *операцій шляху*.
+Будь-яка інтеграція спроєктована так, щоб її було дуже просто використовувати (із залежностями), тож ви можете створити «плагін» для свого застосунку у 2 рядках коду, використовуючи ту саму структуру та синтаксис, що й для ваших *операцій шляху*.
-### Протестовано
+### Протестовано { #tested }
* 100% покриття тестами.
* 100% анотована типами кодова база.
-* Використовується у робочих середовищах.
+* Використовується в production-застосунках.
-## Можливості Starlette
+## Можливості Starlette { #starlette-features }
**FastAPI** повністю сумісний із (та побудований на основі) Starlette. Тому будь-який додатковий код Starlette, який ви маєте, також працюватиме.
-**FastAPI** фактично є підкласом **Starlette**. Тому, якщо ви вже знайомі зі Starlette або використовуєте його, більшість функціональності працюватиме так само.
+`FastAPI` фактично є підкласом `Starlette`. Тому, якщо ви вже знайомі зі Starlette або використовуєте його, більшість функціональності працюватиме так само.
-З **FastAPI** ви отримуєте всі можливості **Starlette** (адже FastAPI — це, по суті, Starlette на стероїдах):
+З **FastAPI** ви отримуєте всі можливості **Starlette** (адже FastAPI — це просто Starlette на стероїдах):
-* Разюча продуктивність. Це один із найшвидших фреймворків на Python, на рівні з **NodeJS** і **Go**.
+* Разюча продуктивність. Це один із найшвидших доступних Python-фреймворків, на рівні з **NodeJS** і **Go**.
* Підтримка **WebSocket**.
* Фонові задачі у процесі.
* Події запуску та завершення роботи.
@@ -163,27 +175,27 @@ FastAPI має розумні налаштування **за замовчува
* 100% покриття тестами.
* 100% анотована типами кодова база.
-## Можливості Pydantic
+## Можливості Pydantic { #pydantic-features }
**FastAPI** повністю сумісний із (та побудований на основі) Pydantic. Тому будь-який додатковий код Pydantic, який ви маєте, також працюватиме.
-Включаючи зовнішні бібліотеки, побудовані також на Pydantic, такі як ORM, ODM для баз даних.
+Включно із зовнішніми бібліотеками, які також базуються на Pydantic, як-от ORM-и, ODM-и для баз даних.
-Це також означає, що в багатьох випадках ви можете передати той самий об'єкт, який отримуєте з запиту, **безпосередньо в базу даних**, оскільки все автоматично перевіряється.
+Це також означає, що в багатьох випадках ви можете передати той самий об'єкт, який отримуєте із запиту, **безпосередньо в базу даних**, оскільки все автоматично перевіряється.
-Те ж саме відбувається й у зворотному напрямку — у багатьох випадках ви можете просто передати об'єкт, який отримуєте з бази даних, **безпосередньо клієнту**.
+Те саме застосовується й у зворотному напрямку — у багатьох випадках ви можете просто передати об'єкт, який отримуєте з бази даних, **безпосередньо клієнту**.
З **FastAPI** ви отримуєте всі можливості **Pydantic** (адже FastAPI базується на Pydantic для обробки всіх даних):
-* **Ніякої плутанини** :
- * Не потрібно вчити нову мову для визначення схем.
+* **Ніякої плутанини**:
+ * Не потрібно вчити нову мікромову для визначення схем.
* Якщо ви знаєте типи Python, ви знаєте, як використовувати Pydantic.
-* Легко працює з вашим **IDE/лінтером/мозком**:
- * Оскільки структури даних Pydantic є просто екземплярами класів, які ви визначаєте; автодоповнення, лінтинг, mypy і ваша інтуїція повинні добре працювати з вашими перевіреними даними.
-* Валідація **складних структур**:
- * Використання ієрархічних моделей Pydantic. Python `typing`, `List` і `Dict` тощо.
- * Валідатори дозволяють чітко і просто визначати, перевіряти й документувати складні схеми даних у вигляді JSON-схеми.
- * Ви можете мати глибоко **вкладені JSON об'єкти** та перевірити та анотувати їх всі.
+* Легко працює з вашим **IDE/linter/мозком**:
+ * Оскільки структури даних pydantic є просто екземплярами класів, які ви визначаєте; автодоповнення, лінтинг, mypy і ваша інтуїція повинні добре працювати з вашими перевіреними даними.
+* Валідує **складні структури**:
+ * Використання ієрархічних моделей Pydantic, Python `typing`’s `List` і `Dict` тощо.
+ * Валідатори дають змогу складні схеми даних чітко й просто визначати, перевіряти й документувати як JSON Schema.
+ * Ви можете мати глибоко **вкладені JSON** об'єкти, і всі вони будуть валідовані та анотовані.
* **Розширюваність**:
- * Pydantic дозволяє визначати користувацькі типи даних або розширювати валідацію методами в моделі декоратором `validator`.
+ * Pydantic дозволяє визначати користувацькі типи даних або ви можете розширити валідацію методами в моделі, позначеними декоратором validator.
* 100% покриття тестами.
diff --git a/docs_src/app_testing/app_b_an_py310/main.py b/docs_src/app_testing/app_b_an_py310/main.py
index c5952be0b..1b77dd137 100644
--- a/docs_src/app_testing/app_b_an_py310/main.py
+++ b/docs_src/app_testing/app_b_an_py310/main.py
@@ -28,11 +28,11 @@ async def read_main(item_id: str, x_token: Annotated[str, Header()]):
return fake_db[item_id]
-@app.post("/items/", response_model=Item)
-async def create_item(item: Item, x_token: Annotated[str, Header()]):
+@app.post("/items/")
+async def create_item(item: Item, x_token: Annotated[str, Header()]) -> Item:
if x_token != fake_secret_token:
raise HTTPException(status_code=400, detail="Invalid X-Token header")
if item.id in fake_db:
raise HTTPException(status_code=409, detail="Item already exists")
- fake_db[item.id] = item
+ fake_db[item.id] = item.model_dump()
return item
diff --git a/docs_src/app_testing/app_b_an_py39/main.py b/docs_src/app_testing/app_b_an_py39/main.py
index 142e23a26..42026a81a 100644
--- a/docs_src/app_testing/app_b_an_py39/main.py
+++ b/docs_src/app_testing/app_b_an_py39/main.py
@@ -28,11 +28,11 @@ async def read_main(item_id: str, x_token: Annotated[str, Header()]):
return fake_db[item_id]
-@app.post("/items/", response_model=Item)
-async def create_item(item: Item, x_token: Annotated[str, Header()]):
+@app.post("/items/")
+async def create_item(item: Item, x_token: Annotated[str, Header()]) -> Item:
if x_token != fake_secret_token:
raise HTTPException(status_code=400, detail="Invalid X-Token header")
if item.id in fake_db:
raise HTTPException(status_code=409, detail="Item already exists")
- fake_db[item.id] = item
+ fake_db[item.id] = item.model_dump()
return item
diff --git a/docs_src/app_testing/app_b_py310/main.py b/docs_src/app_testing/app_b_py310/main.py
index eccedcc7c..83f6fa142 100644
--- a/docs_src/app_testing/app_b_py310/main.py
+++ b/docs_src/app_testing/app_b_py310/main.py
@@ -26,11 +26,11 @@ async def read_main(item_id: str, x_token: str = Header()):
return fake_db[item_id]
-@app.post("/items/", response_model=Item)
-async def create_item(item: Item, x_token: str = Header()):
+@app.post("/items/")
+async def create_item(item: Item, x_token: str = Header()) -> Item:
if x_token != fake_secret_token:
raise HTTPException(status_code=400, detail="Invalid X-Token header")
if item.id in fake_db:
raise HTTPException(status_code=409, detail="Item already exists")
- fake_db[item.id] = item
+ fake_db[item.id] = item.model_dump()
return item
diff --git a/docs_src/app_testing/app_b_py39/main.py b/docs_src/app_testing/app_b_py39/main.py
index 45a103378..ed38f4721 100644
--- a/docs_src/app_testing/app_b_py39/main.py
+++ b/docs_src/app_testing/app_b_py39/main.py
@@ -28,11 +28,11 @@ async def read_main(item_id: str, x_token: str = Header()):
return fake_db[item_id]
-@app.post("/items/", response_model=Item)
-async def create_item(item: Item, x_token: str = Header()):
+@app.post("/items/")
+async def create_item(item: Item, x_token: str = Header()) -> Item:
if x_token != fake_secret_token:
raise HTTPException(status_code=400, detail="Invalid X-Token header")
if item.id in fake_db:
raise HTTPException(status_code=409, detail="Item already exists")
- fake_db[item.id] = item
+ fake_db[item.id] = item.model_dump()
return item
diff --git a/docs_src/body_updates/tutorial002_py310.py b/docs_src/body_updates/tutorial002_py310.py
index e5db71110..d30e41027 100644
--- a/docs_src/body_updates/tutorial002_py310.py
+++ b/docs_src/body_updates/tutorial002_py310.py
@@ -25,8 +25,8 @@ async def read_item(item_id: str):
return items[item_id]
-@app.patch("/items/{item_id}", response_model=Item)
-async def update_item(item_id: str, item: Item):
+@app.patch("/items/{item_id}")
+async def update_item(item_id: str, item: Item) -> Item:
stored_item_data = items[item_id]
stored_item_model = Item(**stored_item_data)
update_data = item.model_dump(exclude_unset=True)
diff --git a/docs_src/body_updates/tutorial002_py39.py b/docs_src/body_updates/tutorial002_py39.py
index eddd7af71..3714b5a55 100644
--- a/docs_src/body_updates/tutorial002_py39.py
+++ b/docs_src/body_updates/tutorial002_py39.py
@@ -27,8 +27,8 @@ async def read_item(item_id: str):
return items[item_id]
-@app.patch("/items/{item_id}", response_model=Item)
-async def update_item(item_id: str, item: Item):
+@app.patch("/items/{item_id}")
+async def update_item(item_id: str, item: Item) -> Item:
stored_item_data = items[item_id]
stored_item_model = Item(**stored_item_data)
update_data = item.model_dump(exclude_unset=True)
diff --git a/docs_src/metadata/tutorial001_1_py39.py b/docs_src/metadata/tutorial001_1_py39.py
index a8f5b9458..419232d86 100644
--- a/docs_src/metadata/tutorial001_1_py39.py
+++ b/docs_src/metadata/tutorial001_1_py39.py
@@ -28,7 +28,7 @@ app = FastAPI(
},
license_info={
"name": "Apache 2.0",
- "identifier": "MIT",
+ "identifier": "Apache-2.0",
},
)
diff --git a/docs_src/path_operation_advanced_configuration/tutorial004_py310.py b/docs_src/path_operation_advanced_configuration/tutorial004_py310.py
index a815a564b..f222b11dc 100644
--- a/docs_src/path_operation_advanced_configuration/tutorial004_py310.py
+++ b/docs_src/path_operation_advanced_configuration/tutorial004_py310.py
@@ -12,8 +12,8 @@ class Item(BaseModel):
tags: set[str] = set()
-@app.post("/items/", response_model=Item, summary="Create an item")
-async def create_item(item: Item):
+@app.post("/items/", summary="Create an item")
+async def create_item(item: Item) -> Item:
"""
Create an item with all the information:
diff --git a/docs_src/path_operation_advanced_configuration/tutorial004_py39.py b/docs_src/path_operation_advanced_configuration/tutorial004_py39.py
index d5fe6705c..8fabe7cb8 100644
--- a/docs_src/path_operation_advanced_configuration/tutorial004_py39.py
+++ b/docs_src/path_operation_advanced_configuration/tutorial004_py39.py
@@ -14,8 +14,8 @@ class Item(BaseModel):
tags: set[str] = set()
-@app.post("/items/", response_model=Item, summary="Create an item")
-async def create_item(item: Item):
+@app.post("/items/", summary="Create an item")
+async def create_item(item: Item) -> Item:
"""
Create an item with all the information:
diff --git a/docs_src/path_operation_configuration/tutorial001_py310.py b/docs_src/path_operation_configuration/tutorial001_py310.py
index da078fdf5..2e7488ea4 100644
--- a/docs_src/path_operation_configuration/tutorial001_py310.py
+++ b/docs_src/path_operation_configuration/tutorial001_py310.py
@@ -12,6 +12,6 @@ class Item(BaseModel):
tags: set[str] = set()
-@app.post("/items/", response_model=Item, status_code=status.HTTP_201_CREATED)
-async def create_item(item: Item):
+@app.post("/items/", status_code=status.HTTP_201_CREATED)
+async def create_item(item: Item) -> Item:
return item
diff --git a/docs_src/path_operation_configuration/tutorial001_py39.py b/docs_src/path_operation_configuration/tutorial001_py39.py
index a9dcbf389..09b318282 100644
--- a/docs_src/path_operation_configuration/tutorial001_py39.py
+++ b/docs_src/path_operation_configuration/tutorial001_py39.py
@@ -14,6 +14,6 @@ class Item(BaseModel):
tags: set[str] = set()
-@app.post("/items/", response_model=Item, status_code=status.HTTP_201_CREATED)
-async def create_item(item: Item):
+@app.post("/items/", status_code=status.HTTP_201_CREATED)
+async def create_item(item: Item) -> Item:
return item
diff --git a/docs_src/path_operation_configuration/tutorial002_py310.py b/docs_src/path_operation_configuration/tutorial002_py310.py
index 9a8af5432..59908ed7c 100644
--- a/docs_src/path_operation_configuration/tutorial002_py310.py
+++ b/docs_src/path_operation_configuration/tutorial002_py310.py
@@ -12,8 +12,8 @@ class Item(BaseModel):
tags: set[str] = set()
-@app.post("/items/", response_model=Item, tags=["items"])
-async def create_item(item: Item):
+@app.post("/items/", tags=["items"])
+async def create_item(item: Item) -> Item:
return item
diff --git a/docs_src/path_operation_configuration/tutorial002_py39.py b/docs_src/path_operation_configuration/tutorial002_py39.py
index e7ced7de7..fca3b0de9 100644
--- a/docs_src/path_operation_configuration/tutorial002_py39.py
+++ b/docs_src/path_operation_configuration/tutorial002_py39.py
@@ -14,8 +14,8 @@ class Item(BaseModel):
tags: set[str] = set()
-@app.post("/items/", response_model=Item, tags=["items"])
-async def create_item(item: Item):
+@app.post("/items/", tags=["items"])
+async def create_item(item: Item) -> Item:
return item
diff --git a/docs_src/path_operation_configuration/tutorial003_py310.py b/docs_src/path_operation_configuration/tutorial003_py310.py
index 3d94afe2c..56bd7e36a 100644
--- a/docs_src/path_operation_configuration/tutorial003_py310.py
+++ b/docs_src/path_operation_configuration/tutorial003_py310.py
@@ -14,9 +14,8 @@ class Item(BaseModel):
@app.post(
"/items/",
- response_model=Item,
summary="Create an item",
description="Create an item with all the information, name, description, price, tax and a set of unique tags",
)
-async def create_item(item: Item):
+async def create_item(item: Item) -> Item:
return item
diff --git a/docs_src/path_operation_configuration/tutorial003_py39.py b/docs_src/path_operation_configuration/tutorial003_py39.py
index 607c5707e..a77fb34d8 100644
--- a/docs_src/path_operation_configuration/tutorial003_py39.py
+++ b/docs_src/path_operation_configuration/tutorial003_py39.py
@@ -16,9 +16,8 @@ class Item(BaseModel):
@app.post(
"/items/",
- response_model=Item,
summary="Create an item",
description="Create an item with all the information, name, description, price, tax and a set of unique tags",
)
-async def create_item(item: Item):
+async def create_item(item: Item) -> Item:
return item
diff --git a/docs_src/path_operation_configuration/tutorial004_py310.py b/docs_src/path_operation_configuration/tutorial004_py310.py
index 4cb8bdd43..44404aa08 100644
--- a/docs_src/path_operation_configuration/tutorial004_py310.py
+++ b/docs_src/path_operation_configuration/tutorial004_py310.py
@@ -12,8 +12,8 @@ class Item(BaseModel):
tags: set[str] = set()
-@app.post("/items/", response_model=Item, summary="Create an item")
-async def create_item(item: Item):
+@app.post("/items/", summary="Create an item")
+async def create_item(item: Item) -> Item:
"""
Create an item with all the information:
diff --git a/docs_src/path_operation_configuration/tutorial004_py39.py b/docs_src/path_operation_configuration/tutorial004_py39.py
index fc25680c5..31dfbff1d 100644
--- a/docs_src/path_operation_configuration/tutorial004_py39.py
+++ b/docs_src/path_operation_configuration/tutorial004_py39.py
@@ -14,8 +14,8 @@ class Item(BaseModel):
tags: set[str] = set()
-@app.post("/items/", response_model=Item, summary="Create an item")
-async def create_item(item: Item):
+@app.post("/items/", summary="Create an item")
+async def create_item(item: Item) -> Item:
"""
Create an item with all the information:
diff --git a/docs_src/path_operation_configuration/tutorial005_py310.py b/docs_src/path_operation_configuration/tutorial005_py310.py
index b176631d8..a4129d600 100644
--- a/docs_src/path_operation_configuration/tutorial005_py310.py
+++ b/docs_src/path_operation_configuration/tutorial005_py310.py
@@ -14,11 +14,10 @@ class Item(BaseModel):
@app.post(
"/items/",
- response_model=Item,
summary="Create an item",
response_description="The created item",
)
-async def create_item(item: Item):
+async def create_item(item: Item) -> Item:
"""
Create an item with all the information:
diff --git a/docs_src/path_operation_configuration/tutorial005_py39.py b/docs_src/path_operation_configuration/tutorial005_py39.py
index ddf29b733..0a53a8f2d 100644
--- a/docs_src/path_operation_configuration/tutorial005_py39.py
+++ b/docs_src/path_operation_configuration/tutorial005_py39.py
@@ -16,11 +16,10 @@ class Item(BaseModel):
@app.post(
"/items/",
- response_model=Item,
summary="Create an item",
response_description="The created item",
)
-async def create_item(item: Item):
+async def create_item(item: Item) -> Item:
"""
Create an item with all the information:
diff --git a/docs_src/security/tutorial004_an_py310.py b/docs_src/security/tutorial004_an_py310.py
index 18ea96bc5..368c743bf 100644
--- a/docs_src/security/tutorial004_an_py310.py
+++ b/docs_src/security/tutorial004_an_py310.py
@@ -133,10 +133,10 @@ async def login_for_access_token(
return Token(access_token=access_token, token_type="bearer")
-@app.get("/users/me/", response_model=User)
+@app.get("/users/me/")
async def read_users_me(
current_user: Annotated[User, Depends(get_current_active_user)],
-):
+) -> User:
return current_user
diff --git a/docs_src/security/tutorial004_an_py39.py b/docs_src/security/tutorial004_an_py39.py
index d3fd29e5a..73b3d456d 100644
--- a/docs_src/security/tutorial004_an_py39.py
+++ b/docs_src/security/tutorial004_an_py39.py
@@ -133,10 +133,10 @@ async def login_for_access_token(
return Token(access_token=access_token, token_type="bearer")
-@app.get("/users/me/", response_model=User)
+@app.get("/users/me/")
async def read_users_me(
current_user: Annotated[User, Depends(get_current_active_user)],
-):
+) -> User:
return current_user
diff --git a/docs_src/security/tutorial004_py310.py b/docs_src/security/tutorial004_py310.py
index cd1dcff46..8d0785b40 100644
--- a/docs_src/security/tutorial004_py310.py
+++ b/docs_src/security/tutorial004_py310.py
@@ -130,8 +130,8 @@ async def login_for_access_token(
return Token(access_token=access_token, token_type="bearer")
-@app.get("/users/me/", response_model=User)
-async def read_users_me(current_user: User = Depends(get_current_active_user)):
+@app.get("/users/me/")
+async def read_users_me(current_user: User = Depends(get_current_active_user)) -> User:
return current_user
diff --git a/docs_src/security/tutorial004_py39.py b/docs_src/security/tutorial004_py39.py
index 130dc699a..e67403d5d 100644
--- a/docs_src/security/tutorial004_py39.py
+++ b/docs_src/security/tutorial004_py39.py
@@ -131,8 +131,8 @@ async def login_for_access_token(
return Token(access_token=access_token, token_type="bearer")
-@app.get("/users/me/", response_model=User)
-async def read_users_me(current_user: User = Depends(get_current_active_user)):
+@app.get("/users/me/")
+async def read_users_me(current_user: User = Depends(get_current_active_user)) -> User:
return current_user
diff --git a/docs_src/security/tutorial005_an_py310.py b/docs_src/security/tutorial005_an_py310.py
index df55951c0..fef0ab71c 100644
--- a/docs_src/security/tutorial005_an_py310.py
+++ b/docs_src/security/tutorial005_an_py310.py
@@ -160,10 +160,10 @@ async def login_for_access_token(
return Token(access_token=access_token, token_type="bearer")
-@app.get("/users/me/", response_model=User)
+@app.get("/users/me/")
async def read_users_me(
current_user: Annotated[User, Depends(get_current_active_user)],
-):
+) -> User:
return current_user
diff --git a/docs_src/security/tutorial005_an_py39.py b/docs_src/security/tutorial005_an_py39.py
index 983c1c22c..1aeba688a 100644
--- a/docs_src/security/tutorial005_an_py39.py
+++ b/docs_src/security/tutorial005_an_py39.py
@@ -160,10 +160,10 @@ async def login_for_access_token(
return Token(access_token=access_token, token_type="bearer")
-@app.get("/users/me/", response_model=User)
+@app.get("/users/me/")
async def read_users_me(
current_user: Annotated[User, Depends(get_current_active_user)],
-):
+) -> User:
return current_user
diff --git a/docs_src/security/tutorial005_py310.py b/docs_src/security/tutorial005_py310.py
index d08e2c59f..412fbf798 100644
--- a/docs_src/security/tutorial005_py310.py
+++ b/docs_src/security/tutorial005_py310.py
@@ -159,8 +159,8 @@ async def login_for_access_token(
return Token(access_token=access_token, token_type="bearer")
-@app.get("/users/me/", response_model=User)
-async def read_users_me(current_user: User = Depends(get_current_active_user)):
+@app.get("/users/me/")
+async def read_users_me(current_user: User = Depends(get_current_active_user)) -> User:
return current_user
diff --git a/docs_src/security/tutorial005_py39.py b/docs_src/security/tutorial005_py39.py
index 5bde47ef4..32280aa48 100644
--- a/docs_src/security/tutorial005_py39.py
+++ b/docs_src/security/tutorial005_py39.py
@@ -160,8 +160,8 @@ async def login_for_access_token(
return Token(access_token=access_token, token_type="bearer")
-@app.get("/users/me/", response_model=User)
-async def read_users_me(current_user: User = Depends(get_current_active_user)):
+@app.get("/users/me/")
+async def read_users_me(current_user: User = Depends(get_current_active_user)) -> User:
return current_user
diff --git a/docs_src/wsgi/tutorial001_py39.py b/docs_src/wsgi/tutorial001_py39.py
index 7f27a85a1..8eeceb829 100644
--- a/docs_src/wsgi/tutorial001_py39.py
+++ b/docs_src/wsgi/tutorial001_py39.py
@@ -1,5 +1,5 @@
+from a2wsgi import WSGIMiddleware
from fastapi import FastAPI
-from fastapi.middleware.wsgi import WSGIMiddleware
from flask import Flask, request
from markupsafe import escape
diff --git a/fastapi/_compat/shared.py b/fastapi/_compat/shared.py
index 419b58f7f..68b9bbdf1 100644
--- a/fastapi/_compat/shared.py
+++ b/fastapi/_compat/shared.py
@@ -17,7 +17,7 @@ from pydantic.version import VERSION as PYDANTIC_VERSION
from starlette.datastructures import UploadFile
from typing_extensions import get_args, get_origin
-# Copy from Pydantic v2, compatible with v1
+# Copy from Pydantic: pydantic/_internal/_typing_extra.py
if sys.version_info < (3, 10):
WithArgsTypes: tuple[Any, ...] = (typing._GenericAlias, types.GenericAlias) # type: ignore[attr-defined]
else:
@@ -45,7 +45,7 @@ sequence_types = tuple(sequence_annotation_to_type.keys())
Url: type[Any]
-# Copy of Pydantic v2, compatible with v1
+# Copy of Pydantic: pydantic/_internal/_utils.py
def lenient_issubclass(
cls: Any, class_or_tuple: Union[type[Any], tuple[type[Any], ...], None]
) -> bool:
diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py
index 25b681453..dae78a32e 100644
--- a/fastapi/_compat/v2.py
+++ b/fastapi/_compat/v2.py
@@ -477,7 +477,7 @@ def get_model_fields(model: type[BaseModel]) -> list[ModelField]:
@lru_cache
def get_cached_model_fields(model: type[BaseModel]) -> list[ModelField]:
- return get_model_fields(model) # type: ignore[return-value]
+ return get_model_fields(model)
# Duplicate of several schema functions from Pydantic v1 to make them compatible with
@@ -500,13 +500,13 @@ def get_model_name_map(unique_models: TypeModelSet) -> dict[TypeModelOrEnum, str
def get_compat_model_name_map(fields: list[ModelField]) -> ModelNameMap:
- all_flat_models = set()
+ all_flat_models: TypeModelSet = set()
v2_model_fields = [field for field in fields if isinstance(field, ModelField)]
v2_flat_models = get_flat_models_from_fields(v2_model_fields, known_models=set())
- all_flat_models = all_flat_models.union(v2_flat_models) # type: ignore[arg-type]
+ all_flat_models = all_flat_models.union(v2_flat_models)
- model_name_map = get_model_name_map(all_flat_models) # type: ignore[arg-type]
+ model_name_map = get_model_name_map(all_flat_models)
return model_name_map
diff --git a/fastapi/applications.py b/fastapi/applications.py
index 54175cb3b..340cabfc2 100644
--- a/fastapi/applications.py
+++ b/fastapi/applications.py
@@ -672,7 +672,7 @@ class FastAPI(Starlette):
in the autogenerated OpenAPI using the `root_path`.
Read more about it in the
- [FastAPI docs for Behind a Proxy](https://fastapi.tiangolo.com/advanced/behind-a-proxy/#disable-automatic-server-from-root_path).
+ [FastAPI docs for Behind a Proxy](https://fastapi.tiangolo.com/advanced/behind-a-proxy/#disable-automatic-server-from-root-path).
**Example**
@@ -739,7 +739,7 @@ class FastAPI(Starlette):
It will be added to the generated OpenAPI (e.g. visible at `/docs`).
Read more about it in the
- [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
+ [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#deprecate-a-path-operation).
"""
),
] = None,
@@ -812,6 +812,9 @@ class FastAPI(Starlette):
In this case, there would be two different schemas, one for input and
another one for output.
+
+ Read more about it in the
+ [FastAPI docs about how to separate schemas for input and output](https://fastapi.tiangolo.com/how-to/separate-openapi-schemas)
"""
),
] = True,
diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py
index ef8549b92..3037b233b 100644
--- a/fastapi/dependencies/utils.py
+++ b/fastapi/dependencies/utils.py
@@ -204,7 +204,12 @@ def _get_signature(call: Callable[..., Any]) -> inspect.Signature:
except NameError:
# Handle type annotations with if TYPE_CHECKING, not used by FastAPI
# e.g. dependency return types
- signature = inspect.signature(call)
+ if sys.version_info >= (3, 14):
+ from annotationlib import Format
+
+ signature = inspect.signature(call, annotation_format=Format.FORWARDREF)
+ else:
+ signature = inspect.signature(call)
else:
signature = inspect.signature(call)
return signature
@@ -399,7 +404,7 @@ def analyze_param(
if isinstance(fastapi_annotation, FieldInfo):
# Copy `field_info` because we mutate `field_info.default` below.
field_info = copy_field_info(
- field_info=fastapi_annotation, # type: ignore[arg-type]
+ field_info=fastapi_annotation,
annotation=use_annotation,
)
assert (
@@ -433,7 +438,7 @@ def analyze_param(
"Cannot specify FastAPI annotations in `Annotated` and default value"
f" together for {param_name!r}"
)
- field_info = value # type: ignore[assignment]
+ field_info = value
if isinstance(field_info, FieldInfo):
field_info.annotation = type_annotation
@@ -520,7 +525,7 @@ def analyze_param(
# For Pydantic v1
and getattr(field, "shape", 1) == 1
)
- )
+ ), f"Query parameter {param_name!r} must be one of the supported types"
return ParamDetails(type_annotation=type_annotation, depends=depends, field=field)
diff --git a/fastapi/encoders.py b/fastapi/encoders.py
index e8610c983..b4661be4f 100644
--- a/fastapi/encoders.py
+++ b/fastapi/encoders.py
@@ -219,9 +219,9 @@ def jsonable_encoder(
if isinstance(obj, encoder_type):
return encoder_instance(obj)
if include is not None and not isinstance(include, (set, dict)):
- include = set(include)
+ include = set(include) # type: ignore[assignment]
if exclude is not None and not isinstance(exclude, (set, dict)):
- exclude = set(exclude)
+ exclude = set(exclude) # type: ignore[assignment]
if isinstance(obj, BaseModel):
obj_dict = obj.model_dump(
mode="json",
diff --git a/fastapi/exceptions.py b/fastapi/exceptions.py
index 1a3abd80c..62b4674de 100644
--- a/fastapi/exceptions.py
+++ b/fastapi/exceptions.py
@@ -49,6 +49,9 @@ class HTTPException(StarletteHTTPException):
Doc(
"""
HTTP status code to send to the client.
+
+ Read more about it in the
+ [FastAPI docs for Handling Errors](https://fastapi.tiangolo.com/tutorial/handling-errors/#use-httpexception)
"""
),
],
@@ -58,6 +61,9 @@ class HTTPException(StarletteHTTPException):
"""
Any data to be sent to the client in the `detail` key of the JSON
response.
+
+ Read more about it in the
+ [FastAPI docs for Handling Errors](https://fastapi.tiangolo.com/tutorial/handling-errors/#use-httpexception)
"""
),
] = None,
@@ -66,6 +72,10 @@ class HTTPException(StarletteHTTPException):
Doc(
"""
Any headers to send to the client in the response.
+
+ Read more about it in the
+ [FastAPI docs for Handling Errors](https://fastapi.tiangolo.com/tutorial/handling-errors/#add-custom-headers)
+
"""
),
] = None,
diff --git a/fastapi/middleware/wsgi.py b/fastapi/middleware/wsgi.py
index c4c6a797d..69e4dcab9 100644
--- a/fastapi/middleware/wsgi.py
+++ b/fastapi/middleware/wsgi.py
@@ -1 +1,3 @@
-from starlette.middleware.wsgi import WSGIMiddleware as WSGIMiddleware # noqa
+from starlette.middleware.wsgi import (
+ WSGIMiddleware as WSGIMiddleware,
+) # pragma: no cover # noqa
diff --git a/fastapi/openapi/docs.py b/fastapi/openapi/docs.py
index 82380f85d..bb387c609 100644
--- a/fastapi/openapi/docs.py
+++ b/fastapi/openapi/docs.py
@@ -33,6 +33,9 @@ def get_swagger_ui_html(
This is normally done automatically by FastAPI using the default URL
`/openapi.json`.
+
+ Read more about it in the
+ [FastAPI docs for Conditional OpenAPI](https://fastapi.tiangolo.com/how-to/conditional-openapi/#conditional-openapi-from-settings-and-env-vars)
"""
),
],
@@ -41,6 +44,9 @@ def get_swagger_ui_html(
Doc(
"""
The HTML `` content, normally shown in the browser tab.
+
+ Read more about it in the
+ [FastAPI docs for Custom Docs UI Static Assets](https://fastapi.tiangolo.com/how-to/custom-docs-ui-assets/)
"""
),
],
@@ -51,6 +57,9 @@ def get_swagger_ui_html(
The URL to use to load the Swagger UI JavaScript.
It is normally set to a CDN URL.
+
+ Read more about it in the
+ [FastAPI docs for Custom Docs UI Static Assets](https://fastapi.tiangolo.com/how-to/custom-docs-ui-assets/)
"""
),
] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js",
@@ -61,6 +70,9 @@ def get_swagger_ui_html(
The URL to use to load the Swagger UI CSS.
It is normally set to a CDN URL.
+
+ Read more about it in the
+ [FastAPI docs for Custom Docs UI Static Assets](https://fastapi.tiangolo.com/how-to/custom-docs-ui-assets/)
"""
),
] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css",
@@ -77,6 +89,9 @@ def get_swagger_ui_html(
Doc(
"""
The OAuth2 redirect URL, it is normally automatically handled by FastAPI.
+
+ Read more about it in the
+ [FastAPI docs for Custom Docs UI Static Assets](https://fastapi.tiangolo.com/how-to/custom-docs-ui-assets/)
"""
),
] = None,
@@ -85,6 +100,9 @@ def get_swagger_ui_html(
Doc(
"""
A dictionary with Swagger UI OAuth2 initialization configurations.
+
+ Read more about the available configuration options in the
+ [Swagger UI docs](https://swagger.io/docs/open-source-tools/swagger-ui/usage/oauth2/).
"""
),
] = None,
@@ -95,6 +113,9 @@ def get_swagger_ui_html(
Configuration parameters for Swagger UI.
It defaults to [swagger_ui_default_parameters][fastapi.openapi.docs.swagger_ui_default_parameters].
+
+ Read more about it in the
+ [FastAPI docs about how to Configure Swagger UI](https://fastapi.tiangolo.com/how-to/configure-swagger-ui/).
"""
),
] = None,
@@ -118,6 +139,7 @@ def get_swagger_ui_html(
+
{title}
@@ -168,6 +190,9 @@ def get_redoc_html(
This is normally done automatically by FastAPI using the default URL
`/openapi.json`.
+
+ Read more about it in the
+ [FastAPI docs for Conditional OpenAPI](https://fastapi.tiangolo.com/how-to/conditional-openapi/#conditional-openapi-from-settings-and-env-vars)
"""
),
],
@@ -176,6 +201,9 @@ def get_redoc_html(
Doc(
"""
The HTML `` content, normally shown in the browser tab.
+
+ Read more about it in the
+ [FastAPI docs for Custom Docs UI Static Assets](https://fastapi.tiangolo.com/how-to/custom-docs-ui-assets/)
"""
),
],
@@ -186,6 +214,9 @@ def get_redoc_html(
The URL to use to load the ReDoc JavaScript.
It is normally set to a CDN URL.
+
+ Read more about it in the
+ [FastAPI docs for Custom Docs UI Static Assets](https://fastapi.tiangolo.com/how-to/custom-docs-ui-assets/)
"""
),
] = "https://cdn.jsdelivr.net/npm/redoc@2/bundles/redoc.standalone.js",
diff --git a/fastapi/openapi/utils.py b/fastapi/openapi/utils.py
index 75ff26102..d56027b50 100644
--- a/fastapi/openapi/utils.py
+++ b/fastapi/openapi/utils.py
@@ -1,3 +1,4 @@
+import copy
import http.client
import inspect
import warnings
@@ -377,7 +378,7 @@ def get_openapi_path(
additional_status_code,
additional_response,
) in route.responses.items():
- process_response = additional_response.copy()
+ process_response = copy.deepcopy(additional_response)
process_response.pop("model", None)
status_code_key = str(additional_status_code).upper()
if status_code_key == "DEFAULT":
diff --git a/fastapi/param_functions.py b/fastapi/param_functions.py
index 0834fd741..9bd92be4c 100644
--- a/fastapi/param_functions.py
+++ b/fastapi/param_functions.py
@@ -79,6 +79,9 @@ def Path( # noqa: N802
Doc(
"""
Human-readable title.
+
+ Read more about it in the
+ [FastAPI docs for Path Parameters and Numeric Validations](https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#declare-metadata)
"""
),
] = None,
@@ -96,6 +99,9 @@ def Path( # noqa: N802
"""
Greater than. If set, value must be greater than this. Only applicable to
numbers.
+
+ Read more about it in the
+ [FastAPI docs about Path parameters numeric validations](https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#number-validations-greater-than-and-less-than-or-equal)
"""
),
] = None,
@@ -105,6 +111,9 @@ def Path( # noqa: N802
"""
Greater than or equal. If set, value must be greater than or equal to
this. Only applicable to numbers.
+
+ Read more about it in the
+ [FastAPI docs about Path parameters numeric validations](https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#number-validations-greater-than-and-less-than-or-equal)
"""
),
] = None,
@@ -113,6 +122,9 @@ def Path( # noqa: N802
Doc(
"""
Less than. If set, value must be less than this. Only applicable to numbers.
+
+ Read more about it in the
+ [FastAPI docs about Path parameters numeric validations](https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#number-validations-greater-than-and-less-than-or-equal)
"""
),
] = None,
@@ -122,6 +134,9 @@ def Path( # noqa: N802
"""
Less than or equal. If set, value must be less than or equal to this.
Only applicable to numbers.
+
+ Read more about it in the
+ [FastAPI docs about Path parameters numeric validations](https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#number-validations-greater-than-and-less-than-or-equal)
"""
),
] = None,
@@ -213,6 +228,9 @@ def Path( # noqa: N802
Doc(
"""
Example values for this field.
+
+ Read more about it in the
+ [FastAPI docs for Declare Request Example Data](https://fastapi.tiangolo.com/tutorial/schema-extra-example/)
"""
),
] = None,
@@ -343,6 +361,9 @@ def Query( # noqa: N802
Doc(
"""
Default value if the parameter field is not set.
+
+ Read more about it in the
+ [FastAPI docs about Query parameters](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#alternative-old-query-as-the-default-value)
"""
),
] = Undefined,
@@ -367,6 +388,9 @@ def Query( # noqa: N802
This will be used to extract the data and for the generated OpenAPI.
It is particularly useful when you can't use the name you want because it
is a Python reserved keyword or similar.
+
+ Read more about it in the
+ [FastAPI docs about Query parameters](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#alias-parameters)
"""
),
] = None,
@@ -402,6 +426,9 @@ def Query( # noqa: N802
Doc(
"""
Human-readable title.
+
+ Read more about it in the
+ [FastAPI docs about Query parameters](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#declare-more-metadata)
"""
),
] = None,
@@ -410,6 +437,9 @@ def Query( # noqa: N802
Doc(
"""
Human-readable description.
+
+ Read more about it in the
+ [FastAPI docs about Query parameters](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#declare-more-metadata)
"""
),
] = None,
@@ -419,6 +449,9 @@ def Query( # noqa: N802
"""
Greater than. If set, value must be greater than this. Only applicable to
numbers.
+
+ Read more about it in the
+ [FastAPI docs about Path parameters numeric validations](https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#number-validations-greater-than-and-less-than-or-equal)
"""
),
] = None,
@@ -428,6 +461,9 @@ def Query( # noqa: N802
"""
Greater than or equal. If set, value must be greater than or equal to
this. Only applicable to numbers.
+
+ Read more about it in the
+ [FastAPI docs about Path parameters numeric validations](https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#number-validations-greater-than-and-less-than-or-equal)
"""
),
] = None,
@@ -436,6 +472,9 @@ def Query( # noqa: N802
Doc(
"""
Less than. If set, value must be less than this. Only applicable to numbers.
+
+ Read more about it in the
+ [FastAPI docs about Path parameters numeric validations](https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#number-validations-greater-than-and-less-than-or-equal)
"""
),
] = None,
@@ -445,6 +484,9 @@ def Query( # noqa: N802
"""
Less than or equal. If set, value must be less than or equal to this.
Only applicable to numbers.
+
+ Read more about it in the
+ [FastAPI docs about Path parameters numeric validations](https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#number-validations-greater-than-and-less-than-or-equal)
"""
),
] = None,
@@ -453,6 +495,9 @@ def Query( # noqa: N802
Doc(
"""
Minimum length for strings.
+
+ Read more about it in the
+ [FastAPI docs about Query parameters](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/)
"""
),
] = None,
@@ -461,6 +506,9 @@ def Query( # noqa: N802
Doc(
"""
Maximum length for strings.
+
+ Read more about it in the
+ [FastAPI docs about Query parameters](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/)
"""
),
] = None,
@@ -469,6 +517,9 @@ def Query( # noqa: N802
Doc(
"""
RegEx pattern for strings.
+
+ Read more about it in the
+ [FastAPI docs about Query parameters](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#add-regular-expressions
"""
),
] = None,
@@ -536,6 +587,9 @@ def Query( # noqa: N802
Doc(
"""
Example values for this field.
+
+ Read more about it in the
+ [FastAPI docs for Declare Request Example Data](https://fastapi.tiangolo.com/tutorial/schema-extra-example/)
"""
),
] = None,
@@ -570,6 +624,9 @@ def Query( # noqa: N802
Mark this parameter field as deprecated.
It will affect the generated OpenAPI (e.g. visible at `/docs`).
+
+ Read more about it in the
+ [FastAPI docs about Query parameters](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#deprecating-parameters)
"""
),
] = None,
@@ -581,6 +638,9 @@ def Query( # noqa: N802
You probably don't need it, but it's available.
This affects the generated OpenAPI (e.g. visible at `/docs`).
+
+ Read more about it in the
+ [FastAPI docs about Query parameters](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi
"""
),
] = True,
@@ -849,6 +909,9 @@ def Header( # noqa: N802
Doc(
"""
Example values for this field.
+
+ Read more about it in the
+ [FastAPI docs for Declare Request Example Data](https://fastapi.tiangolo.com/tutorial/schema-extra-example/)
"""
),
] = None,
@@ -1152,6 +1215,9 @@ def Cookie( # noqa: N802
Doc(
"""
Example values for this field.
+
+ Read more about it in the
+ [FastAPI docs for Declare Request Example Data](https://fastapi.tiangolo.com/tutorial/schema-extra-example/)
"""
),
] = None,
@@ -1477,6 +1543,9 @@ def Body( # noqa: N802
Doc(
"""
Example values for this field.
+
+ Read more about it in the
+ [FastAPI docs for Declare Request Example Data](https://fastapi.tiangolo.com/tutorial/schema-extra-example/)
"""
),
] = None,
@@ -1790,6 +1859,9 @@ def Form( # noqa: N802
Doc(
"""
Example values for this field.
+
+ Read more about it in the
+ [FastAPI docs for Declare Request Example Data](https://fastapi.tiangolo.com/tutorial/schema-extra-example/)
"""
),
] = None,
@@ -2102,6 +2174,9 @@ def File( # noqa: N802
Doc(
"""
Example values for this field.
+
+ Read more about it in the
+ [FastAPI docs for Declare Request Example Data](https://fastapi.tiangolo.com/tutorial/schema-extra-example/)
"""
),
] = None,
@@ -2215,6 +2290,9 @@ def Depends( # noqa: N802
Don't call it directly, FastAPI will call it for you, just pass the object
directly.
+
+ Read more about it in the
+ [FastAPI docs for Dependencies](https://fastapi.tiangolo.com/tutorial/dependencies/)
"""
),
] = None,
@@ -2230,6 +2308,9 @@ def Depends( # noqa: N802
Set `use_cache` to `False` to disable this behavior and ensure the
dependency is called again (if declared more than once) in the same request.
+
+ Read more about it in the
+ [FastAPI docs about sub-dependencies](https://fastapi.tiangolo.com/tutorial/dependencies/sub-dependencies/#using-the-same-dependency-multiple-times)
"""
),
] = True,
@@ -2250,6 +2331,9 @@ def Depends( # noqa: N802
that handles the request (similar to when using `"function"`), but end
**after** the response is sent back to the client. So, the dependency
function will be executed **around** the **request** and response cycle.
+
+ Read more about it in the
+ [FastAPI docs for FastAPI Dependencies with yield](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-with-yield/#early-exit-and-scope)
"""
),
] = None,
@@ -2295,6 +2379,9 @@ def Security( # noqa: N802
Don't call it directly, FastAPI will call it for you, just pass the object
directly.
+
+ Read more about it in the
+ [FastAPI docs for Dependencies](https://fastapi.tiangolo.com/tutorial/dependencies/)
"""
),
] = None,
@@ -2312,7 +2399,9 @@ def Security( # noqa: N802
These scopes are integrated with OpenAPI (and the API docs at `/docs`).
So they are visible in the OpenAPI specification.
- )
+
+ Read more about it in the
+ [FastAPI docs about OAuth2 scopes](https://fastapi.tiangolo.com/advanced/security/oauth2-scopes/)
"""
),
] = None,
@@ -2327,6 +2416,9 @@ def Security( # noqa: N802
Set `use_cache` to `False` to disable this behavior and ensure the
dependency is called again (if declared more than once) in the same request.
+
+ Read more about it in the
+ [FastAPI docs about sub-dependencies](https://fastapi.tiangolo.com/tutorial/dependencies/sub-dependencies/#using-the-same-dependency-multiple-times)
"""
),
] = True,
diff --git a/fastapi/security/oauth2.py b/fastapi/security/oauth2.py
index fc49ba190..58ffc5c76 100644
--- a/fastapi/security/oauth2.py
+++ b/fastapi/security/oauth2.py
@@ -68,6 +68,9 @@ class OAuth2PasswordRequestForm:
"password". Nevertheless, this dependency class is permissive and
allows not passing it. If you want to enforce it, use instead the
`OAuth2PasswordRequestFormStrict` dependency.
+
+ Read more about it in the
+ [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/).
"""
),
] = None,
@@ -78,6 +81,9 @@ class OAuth2PasswordRequestForm:
"""
`username` string. The OAuth2 spec requires the exact field name
`username`.
+
+ Read more about it in the
+ [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/).
"""
),
],
@@ -88,6 +94,9 @@ class OAuth2PasswordRequestForm:
"""
`password` string. The OAuth2 spec requires the exact field name
`password`.
+
+ Read more about it in the
+ [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/).
"""
),
],
@@ -112,6 +121,9 @@ class OAuth2PasswordRequestForm:
* `users:read`
* `profile`
* `openid`
+
+ Read more about it in the
+ [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/).
"""
),
] = "",
@@ -222,6 +234,9 @@ class OAuth2PasswordRequestFormStrict(OAuth2PasswordRequestForm):
"password". This dependency is strict about it. If you want to be
permissive, use instead the `OAuth2PasswordRequestForm` dependency
class.
+
+ Read more about it in the
+ [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/).
"""
),
],
@@ -232,6 +247,9 @@ class OAuth2PasswordRequestFormStrict(OAuth2PasswordRequestForm):
"""
`username` string. The OAuth2 spec requires the exact field name
`username`.
+
+ Read more about it in the
+ [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/).
"""
),
],
@@ -242,6 +260,9 @@ class OAuth2PasswordRequestFormStrict(OAuth2PasswordRequestForm):
"""
`password` string. The OAuth2 spec requires the exact field name
`password`.
+
+ Read more about it in the
+ [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/).
"""
),
],
@@ -266,6 +287,9 @@ class OAuth2PasswordRequestFormStrict(OAuth2PasswordRequestForm):
* `users:read`
* `profile`
* `openid`
+
+ Read more about it in the
+ [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/).
"""
),
] = "",
@@ -423,6 +447,9 @@ class OAuth2PasswordBearer(OAuth2):
"""
The URL to obtain the OAuth2 token. This would be the *path operation*
that has `OAuth2PasswordRequestForm` as a dependency.
+
+ Read more about it in the
+ [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/).
"""
),
],
@@ -442,6 +469,9 @@ class OAuth2PasswordBearer(OAuth2):
"""
The OAuth2 scopes that would be required by the *path operations* that
use this dependency.
+
+ Read more about it in the
+ [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/).
"""
),
] = None,
diff --git a/fastapi/security/utils.py b/fastapi/security/utils.py
index 002e68b44..fd349aec7 100644
--- a/fastapi/security/utils.py
+++ b/fastapi/security/utils.py
@@ -7,4 +7,4 @@ def get_authorization_scheme_param(
if not authorization_header_value:
return "", ""
scheme, _, param = authorization_header_value.partition(" ")
- return scheme, param
+ return scheme, param.strip()
diff --git a/fastapi/types.py b/fastapi/types.py
index d3e980cb4..1c3a6de74 100644
--- a/fastapi/types.py
+++ b/fastapi/types.py
@@ -3,9 +3,9 @@ from enum import Enum
from typing import Any, Callable, Optional, TypeVar, Union
from pydantic import BaseModel
+from pydantic.main import IncEx as IncEx
DecoratedCallable = TypeVar("DecoratedCallable", bound=Callable[..., Any])
UnionType = getattr(types, "UnionType", Union)
ModelNameMap = dict[Union[type[BaseModel], type[Enum]], str]
-IncEx = Union[set[int], set[str], dict[int, Any], dict[str, Any]]
DependencyCacheKey = tuple[Optional[Callable[..., Any]], tuple[str, ...], str]
diff --git a/fastapi/utils.py b/fastapi/utils.py
index 78fdcbb5b..1c3a0881f 100644
--- a/fastapi/utils.py
+++ b/fastapi/utils.py
@@ -90,7 +90,7 @@ def create_model_field(
field_info = field_info or FieldInfo(annotation=type_, default=default, alias=alias)
kwargs = {"mode": mode, "name": name, "field_info": field_info}
try:
- return v2.ModelField(**kwargs) # type: ignore[return-value,arg-type]
+ return v2.ModelField(**kwargs) # type: ignore[arg-type]
except PydanticSchemaGenerationError:
raise fastapi.exceptions.FastAPIError(
_invalid_args_message.format(type_=type_)
diff --git a/pyproject.toml b/pyproject.toml
index fe9b3a7ea..0f6bf1e4a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -140,18 +140,18 @@ docs = [
"jieba==0.42.1",
"markdown-include-variants==0.0.8",
"mdx-include>=1.4.1,<2.0.0",
- "mkdocs-macros-plugin==1.4.1",
+ "mkdocs-macros-plugin==1.5.0",
"mkdocs-material==9.7.0",
"mkdocs-redirects>=1.2.1,<1.3.0",
"mkdocstrings[python]==0.30.1",
"pillow==11.3.0",
"python-slugify==8.0.4",
"pyyaml>=5.3.1,<7.0.0",
- "typer==0.16.0",
+ "typer==0.21.1",
]
docs-tests = [
"httpx>=0.23.0,<1.0.0",
- "ruff==0.14.3",
+ "ruff==0.14.14",
]
github-actions = [
"httpx>=0.27.0,<1.0.0",
@@ -174,13 +174,14 @@ tests = [
"pytest>=7.1.3,<9.0.0",
"pytest-codspeed==4.2.0",
"pyyaml>=5.3.1,<7.0.0",
- "sqlmodel==0.0.27",
+ "sqlmodel==0.0.31",
"strawberry-graphql>=0.200.0,<1.0.0",
"types-orjson==3.6.2",
"types-ujson==5.10.0.20240515",
+ "a2wsgi>=1.9.0,<=2.0.0",
]
translations = [
- "gitpython==3.1.45",
+ "gitpython==3.1.46",
"pydantic-ai==0.4.10",
"pygithub==2.8.1",
]
@@ -231,7 +232,6 @@ xfail_strict = true
junit_family = "xunit2"
filterwarnings = [
"error",
- 'ignore:starlette.middleware.wsgi is deprecated and will be removed in a future release\..*:DeprecationWarning:starlette',
# see https://trio.readthedocs.io/en/stable/history.html#trio-0-22-0-2022-09-28
"ignore:You seem to already have a custom.*:RuntimeWarning:trio",
# TODO: remove after upgrading SQLAlchemy to a version that includes the following changes
diff --git a/scripts/mkdocs_hooks.py b/scripts/mkdocs_hooks.py
index 4b781270a..567c0111d 100644
--- a/scripts/mkdocs_hooks.py
+++ b/scripts/mkdocs_hooks.py
@@ -26,6 +26,17 @@ def get_missing_translation_content(docs_dir: str) -> str:
return missing_translation_path.read_text(encoding="utf-8")
+@lru_cache
+def get_translation_banner_content(docs_dir: str) -> str:
+ docs_dir_path = Path(docs_dir)
+ translation_banner_path = docs_dir_path / "translation-banner.md"
+ if not translation_banner_path.is_file():
+ translation_banner_path = (
+ docs_dir_path.parent.parent / "en" / "docs" / "translation-banner.md"
+ )
+ return translation_banner_path.read_text(encoding="utf-8")
+
+
@lru_cache
def get_mkdocs_material_langs() -> list[str]:
material_path = Path(material.__file__).parent
@@ -151,4 +162,21 @@ def on_page_markdown(
if markdown.startswith("#"):
header, _, body = markdown.partition("\n\n")
return f"{header}\n\n{missing_translation_content}\n\n{body}"
- return markdown
+
+ docs_dir_path = Path(config.docs_dir)
+ en_docs_dir_path = docs_dir_path.parent.parent / "en/docs"
+
+ if docs_dir_path == en_docs_dir_path:
+ return markdown
+
+ # For translated pages add translation banner
+ translation_banner_content = get_translation_banner_content(config.docs_dir)
+ en_url = "https://fastapi.tiangolo.com/" + page.url.lstrip("/")
+ translation_banner_content = translation_banner_content.replace(
+ "ENGLISH_VERSION_URL", en_url
+ )
+ header = ""
+ body = markdown
+ if markdown.startswith("#"):
+ header, _, body = markdown.partition("\n\n")
+ return f"{header}\n\n{translation_banner_content}\n\n{body}"
diff --git a/scripts/translate.py b/scripts/translate.py
index eba4ad6a2..9eda7b390 100644
--- a/scripts/translate.py
+++ b/scripts/translate.py
@@ -10,6 +10,7 @@ from typing import Annotated
import git
import typer
import yaml
+from doc_parsing_utils import check_translation
from github import Github
from pydantic_ai import Agent
from rich import print
@@ -119,9 +120,30 @@ def translate_page(
]
)
prompt = "\n\n".join(prompt_segments)
- print(f"Running agent for {out_path}")
- result = agent.run_sync(prompt)
- out_content = f"{result.output.strip()}\n"
+
+ MAX_ATTEMPTS = 3
+ for attempt_no in range(1, MAX_ATTEMPTS + 1):
+ print(f"Running agent for {out_path} (attempt {attempt_no}/{MAX_ATTEMPTS})")
+ result = agent.run_sync(prompt)
+ out_content = f"{result.output.strip()}\n"
+ try:
+ check_translation(
+ doc_lines=out_content.splitlines(),
+ en_doc_lines=original_content.splitlines(),
+ lang_code=language,
+ auto_fix=False,
+ path=str(out_path),
+ )
+ break # Exit loop if no errors
+ except ValueError as e:
+ print(
+ f"Translation check failed on attempt {attempt_no}/{MAX_ATTEMPTS}: {e}"
+ )
+ continue # Retry if not reached max attempts
+ else: # Max retry attempts reached
+ print(f"Translation failed for {out_path} after {MAX_ATTEMPTS} attempts")
+ raise typer.Exit(code=1)
+
print(f"Saving translation to {out_path}")
out_path.write_text(out_content, encoding="utf-8", newline="\n")
diff --git a/tests/test_additional_responses_union_duplicate_anyof.py b/tests/test_additional_responses_union_duplicate_anyof.py
new file mode 100644
index 000000000..f5d987ca3
--- /dev/null
+++ b/tests/test_additional_responses_union_duplicate_anyof.py
@@ -0,0 +1,123 @@
+"""
+Regression test: Ensure app-level responses with Union models and content/examples
+don't accumulate duplicate $ref entries in anyOf arrays.
+See https://github.com/fastapi/fastapi/pull/14463
+"""
+
+from typing import Union
+
+from fastapi import FastAPI
+from fastapi.testclient import TestClient
+from pydantic import BaseModel
+
+
+class ModelA(BaseModel):
+ a: str
+
+
+class ModelB(BaseModel):
+ b: str
+
+
+app = FastAPI(
+ responses={
+ 500: {
+ "model": Union[ModelA, ModelB],
+ "content": {"application/json": {"examples": {"Case A": {"value": "a"}}}},
+ }
+ }
+)
+
+
+@app.get("/route1")
+async def route1():
+ pass # pragma: no cover
+
+
+@app.get("/route2")
+async def route2():
+ pass # pragma: no cover
+
+
+client = TestClient(app)
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/route1": {
+ "get": {
+ "summary": "Route1",
+ "operationId": "route1_route1_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {"$ref": "#/components/schemas/ModelA"},
+ {"$ref": "#/components/schemas/ModelB"},
+ ],
+ "title": "Response 500 Route1 Route1 Get",
+ },
+ "examples": {"Case A": {"value": "a"}},
+ }
+ },
+ },
+ },
+ }
+ },
+ "/route2": {
+ "get": {
+ "summary": "Route2",
+ "operationId": "route2_route2_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "500": {
+ "description": "Internal Server Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {"$ref": "#/components/schemas/ModelA"},
+ {"$ref": "#/components/schemas/ModelB"},
+ ],
+ "title": "Response 500 Route2 Route2 Get",
+ },
+ "examples": {"Case A": {"value": "a"}},
+ }
+ },
+ },
+ },
+ }
+ },
+ },
+ "components": {
+ "schemas": {
+ "ModelA": {
+ "properties": {"a": {"type": "string", "title": "A"}},
+ "type": "object",
+ "required": ["a"],
+ "title": "ModelA",
+ },
+ "ModelB": {
+ "properties": {"b": {"type": "string", "title": "B"}},
+ "type": "object",
+ "required": ["b"],
+ "title": "ModelB",
+ },
+ }
+ },
+ }
diff --git a/tests/test_invalid_sequence_param.py b/tests/test_invalid_sequence_param.py
index 2b8fd059e..3695344f7 100644
--- a/tests/test_invalid_sequence_param.py
+++ b/tests/test_invalid_sequence_param.py
@@ -6,7 +6,10 @@ from pydantic import BaseModel
def test_invalid_sequence():
- with pytest.raises(AssertionError):
+ with pytest.raises(
+ AssertionError,
+ match="Query parameter 'q' must be one of the supported types",
+ ):
app = FastAPI()
class Item(BaseModel):
@@ -18,7 +21,10 @@ def test_invalid_sequence():
def test_invalid_tuple():
- with pytest.raises(AssertionError):
+ with pytest.raises(
+ AssertionError,
+ match="Query parameter 'q' must be one of the supported types",
+ ):
app = FastAPI()
class Item(BaseModel):
@@ -30,7 +36,10 @@ def test_invalid_tuple():
def test_invalid_dict():
- with pytest.raises(AssertionError):
+ with pytest.raises(
+ AssertionError,
+ match="Query parameter 'q' must be one of the supported types",
+ ):
app = FastAPI()
class Item(BaseModel):
@@ -42,7 +51,10 @@ def test_invalid_dict():
def test_invalid_simple_dict():
- with pytest.raises(AssertionError):
+ with pytest.raises(
+ AssertionError,
+ match="Query parameter 'q' must be one of the supported types",
+ ):
app = FastAPI()
class Item(BaseModel):
diff --git a/tests/test_security_http_base.py b/tests/test_security_http_base.py
index 8cf259a75..ac38ee718 100644
--- a/tests/test_security_http_base.py
+++ b/tests/test_security_http_base.py
@@ -21,6 +21,12 @@ def test_security_http_base():
assert response.json() == {"scheme": "Other", "credentials": "foobar"}
+def test_security_http_base_with_whitespaces():
+ response = client.get("/users/me", headers={"Authorization": "Other foobar "})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"scheme": "Other", "credentials": "foobar"}
+
+
def test_security_http_base_no_credentials():
response = client.get("/users/me")
assert response.status_code == 401, response.text
diff --git a/tests/test_security_oauth2_authorization_code_bearer.py b/tests/test_security_oauth2_authorization_code_bearer.py
index f2097b149..66f53ab00 100644
--- a/tests/test_security_oauth2_authorization_code_bearer.py
+++ b/tests/test_security_oauth2_authorization_code_bearer.py
@@ -37,6 +37,12 @@ def test_token():
assert response.json() == {"token": "testtoken"}
+def test_token_with_whitespaces():
+ response = client.get("/items", headers={"Authorization": "Bearer testtoken "})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"token": "testtoken"}
+
+
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
diff --git a/tests/test_stringified_annotation_dependency_py314.py b/tests/test_stringified_annotation_dependency_py314.py
new file mode 100644
index 000000000..da9b429fc
--- /dev/null
+++ b/tests/test_stringified_annotation_dependency_py314.py
@@ -0,0 +1,30 @@
+from typing import TYPE_CHECKING, Annotated
+
+from fastapi import Depends, FastAPI
+from fastapi.testclient import TestClient
+
+from .utils import needs_py314
+
+if TYPE_CHECKING: # pragma: no cover
+
+ class DummyUser: ...
+
+
+@needs_py314
+def test_stringified_annotation():
+ # python3.14: Use forward reference without "from __future__ import annotations"
+ async def get_current_user() -> DummyUser | None:
+ return None
+
+ app = FastAPI()
+
+ client = TestClient(app)
+
+ @app.get("/")
+ async def get(
+ current_user: Annotated[DummyUser | None, Depends(get_current_user)],
+ ) -> str:
+ return "hello world"
+
+ response = client.get("/")
+ assert response.status_code == 200
diff --git a/tests/test_tutorial/test_dependencies/test_tutorial008.py b/tests/test_tutorial/test_dependencies/test_tutorial008.py
index 9d7377ebe..5a2d226bf 100644
--- a/tests/test_tutorial/test_dependencies/test_tutorial008.py
+++ b/tests/test_tutorial/test_dependencies/test_tutorial008.py
@@ -1,4 +1,5 @@
import importlib
+import sys
from types import ModuleType
from typing import Annotated, Any
from unittest.mock import Mock, patch
@@ -12,8 +13,13 @@ from fastapi.testclient import TestClient
name="module",
params=[
"tutorial008_py39",
- # Fails with `NameError: name 'DepA' is not defined`
- pytest.param("tutorial008_an_py39", marks=pytest.mark.xfail),
+ pytest.param(
+ "tutorial008_an_py39",
+ marks=pytest.mark.xfail(
+ sys.version_info < (3, 14),
+ reason="Fails with `NameError: name 'DepA' is not defined`",
+ ),
+ ),
],
)
def get_module(request: pytest.FixtureRequest):
diff --git a/tests/test_tutorial/test_metadata/test_tutorial001_1.py b/tests/test_tutorial/test_metadata/test_tutorial001_1.py
index 40878ccfd..10cb35c54 100644
--- a/tests/test_tutorial/test_metadata/test_tutorial001_1.py
+++ b/tests/test_tutorial/test_metadata/test_tutorial001_1.py
@@ -28,7 +28,7 @@ def test_openapi_schema():
},
"license": {
"name": "Apache 2.0",
- "identifier": "MIT",
+ "identifier": "Apache-2.0",
},
"version": "0.0.1",
},
diff --git a/tests/utils.py b/tests/utils.py
index efa0bfd52..4cbfee79f 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -6,8 +6,8 @@ needs_py39 = pytest.mark.skipif(sys.version_info < (3, 9), reason="requires pyth
needs_py310 = pytest.mark.skipif(
sys.version_info < (3, 10), reason="requires python3.10+"
)
-needs_py_lt_314 = pytest.mark.skipif(
- sys.version_info >= (3, 14), reason="requires python3.13-"
+needs_py314 = pytest.mark.skipif(
+ sys.version_info < (3, 14), reason="requires python3.14+"
)
diff --git a/uv.lock b/uv.lock
index 9ae2220e0..931a27021 100644
--- a/uv.lock
+++ b/uv.lock
@@ -7,6 +7,18 @@ resolution-markers = [
"python_full_version < '3.10'",
]
+[[package]]
+name = "a2wsgi"
+version = "1.10.10"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions", marker = "python_full_version < '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/9a/cb/822c56fbea97e9eee201a2e434a80437f6750ebcb1ed307ee3a0a7505b14/a2wsgi-1.10.10.tar.gz", hash = "sha256:a5bcffb52081ba39df0d5e9a884fc6f819d92e3a42389343ba77cbf809fe1f45", size = 18799, upload-time = "2025-06-18T09:00:10.843Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/02/d5/349aba3dc421e73cbd4958c0ce0a4f1aa3a738bc0d7de75d2f40ed43a535/a2wsgi-1.10.10-py3-none-any.whl", hash = "sha256:d2b21379479718539dc15fce53b876251a0efe7615352dfe49f6ad1bc507848d", size = 17389, upload-time = "2025-06-18T09:00:09.676Z" },
+]
+
[[package]]
name = "ag-ui-protocol"
version = "0.1.10"
@@ -1058,6 +1070,7 @@ standard-no-fastapi-cloud-cli = [
[package.dev-dependencies]
dev = [
+ { name = "a2wsgi" },
{ name = "anyio", extra = ["trio"] },
{ name = "black" },
{ name = "cairosvg" },
@@ -1131,6 +1144,7 @@ github-actions = [
{ name = "smokeshow" },
]
tests = [
+ { name = "a2wsgi" },
{ name = "anyio", extra = ["trio"] },
{ name = "coverage", version = "7.10.7", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version < '3.10'" },
{ name = "coverage", version = "7.13.1", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version >= '3.10'" },
@@ -1197,13 +1211,14 @@ provides-extras = ["standard", "standard-no-fastapi-cloud-cli", "all"]
[package.metadata.requires-dev]
dev = [
+ { name = "a2wsgi", specifier = ">=1.9.0,<=2.0.0" },
{ name = "anyio", extras = ["trio"], specifier = ">=3.2.1,<5.0.0" },
{ name = "black", specifier = "==25.1.0" },
{ name = "cairosvg", specifier = "==2.8.2" },
{ name = "coverage", extras = ["toml"], specifier = ">=6.5.0,<8.0" },
{ name = "dirty-equals", specifier = "==0.9.0" },
{ name = "flask", specifier = ">=1.1.2,<4.0.0" },
- { name = "gitpython", specifier = "==3.1.45" },
+ { name = "gitpython", specifier = "==3.1.46" },
{ name = "griffe-typingdoc", specifier = "==0.3.0" },
{ name = "griffe-warnings-deprecated", specifier = "==1.1.0" },
{ name = "httpx", specifier = ">=0.23.0,<1.0.0" },
@@ -1211,7 +1226,7 @@ dev = [
{ name = "jieba", specifier = "==0.42.1" },
{ name = "markdown-include-variants", specifier = "==0.0.8" },
{ name = "mdx-include", specifier = ">=1.4.1,<2.0.0" },
- { name = "mkdocs-macros-plugin", specifier = "==1.4.1" },
+ { name = "mkdocs-macros-plugin", specifier = "==1.5.0" },
{ name = "mkdocs-material", specifier = "==9.7.0" },
{ name = "mkdocs-redirects", specifier = ">=1.2.1,<1.3.0" },
{ name = "mkdocstrings", extras = ["python"], specifier = "==0.30.1" },
@@ -1227,10 +1242,10 @@ dev = [
{ name = "pytest-codspeed", specifier = "==4.2.0" },
{ name = "python-slugify", specifier = "==8.0.4" },
{ name = "pyyaml", specifier = ">=5.3.1,<7.0.0" },
- { name = "ruff", specifier = "==0.14.3" },
- { name = "sqlmodel", specifier = "==0.0.27" },
+ { name = "ruff", specifier = "==0.14.14" },
+ { name = "sqlmodel", specifier = "==0.0.31" },
{ name = "strawberry-graphql", specifier = ">=0.200.0,<1.0.0" },
- { name = "typer", specifier = "==0.16.0" },
+ { name = "typer", specifier = "==0.21.1" },
{ name = "types-orjson", specifier = "==3.6.2" },
{ name = "types-ujson", specifier = "==5.10.0.20240515" },
]
@@ -1243,19 +1258,19 @@ docs = [
{ name = "jieba", specifier = "==0.42.1" },
{ name = "markdown-include-variants", specifier = "==0.0.8" },
{ name = "mdx-include", specifier = ">=1.4.1,<2.0.0" },
- { name = "mkdocs-macros-plugin", specifier = "==1.4.1" },
+ { name = "mkdocs-macros-plugin", specifier = "==1.5.0" },
{ name = "mkdocs-material", specifier = "==9.7.0" },
{ name = "mkdocs-redirects", specifier = ">=1.2.1,<1.3.0" },
{ name = "mkdocstrings", extras = ["python"], specifier = "==0.30.1" },
{ name = "pillow", specifier = "==11.3.0" },
{ name = "python-slugify", specifier = "==8.0.4" },
{ name = "pyyaml", specifier = ">=5.3.1,<7.0.0" },
- { name = "ruff", specifier = "==0.14.3" },
- { name = "typer", specifier = "==0.16.0" },
+ { name = "ruff", specifier = "==0.14.14" },
+ { name = "typer", specifier = "==0.21.1" },
]
docs-tests = [
{ name = "httpx", specifier = ">=0.23.0,<1.0.0" },
- { name = "ruff", specifier = "==0.14.3" },
+ { name = "ruff", specifier = "==0.14.14" },
]
github-actions = [
{ name = "httpx", specifier = ">=0.27.0,<1.0.0" },
@@ -1266,6 +1281,7 @@ github-actions = [
{ name = "smokeshow", specifier = ">=0.5.0" },
]
tests = [
+ { name = "a2wsgi", specifier = ">=1.9.0,<=2.0.0" },
{ name = "anyio", extras = ["trio"], specifier = ">=3.2.1,<5.0.0" },
{ name = "coverage", extras = ["toml"], specifier = ">=6.5.0,<8.0" },
{ name = "dirty-equals", specifier = "==0.9.0" },
@@ -1278,14 +1294,14 @@ tests = [
{ name = "pytest", specifier = ">=7.1.3,<9.0.0" },
{ name = "pytest-codspeed", specifier = "==4.2.0" },
{ name = "pyyaml", specifier = ">=5.3.1,<7.0.0" },
- { name = "ruff", specifier = "==0.14.3" },
- { name = "sqlmodel", specifier = "==0.0.27" },
+ { name = "ruff", specifier = "==0.14.14" },
+ { name = "sqlmodel", specifier = "==0.0.31" },
{ name = "strawberry-graphql", specifier = ">=0.200.0,<1.0.0" },
{ name = "types-orjson", specifier = "==3.6.2" },
{ name = "types-ujson", specifier = "==5.10.0.20240515" },
]
translations = [
- { name = "gitpython", specifier = "==3.1.45" },
+ { name = "gitpython", specifier = "==3.1.46" },
{ name = "pydantic-ai", specifier = "==0.4.10" },
{ name = "pygithub", specifier = "==2.8.1" },
]
@@ -1632,15 +1648,15 @@ wheels = [
[[package]]
name = "gitpython"
-version = "3.1.45"
+version = "3.1.46"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "gitdb" },
{ name = "typing-extensions", marker = "python_full_version < '3.10'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/9a/c8/dd58967d119baab745caec2f9d853297cec1989ec1d63f677d3880632b88/gitpython-3.1.45.tar.gz", hash = "sha256:85b0ee964ceddf211c41b9f27a49086010a190fd8132a24e21f362a4b36a791c", size = 215076, upload-time = "2025-07-24T03:45:54.871Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/df/b5/59d16470a1f0dfe8c793f9ef56fd3826093fc52b3bd96d6b9d6c26c7e27b/gitpython-3.1.46.tar.gz", hash = "sha256:400124c7d0ef4ea03f7310ac2fbf7151e09ff97f2a3288d64a440c584a29c37f", size = 215371, upload-time = "2026-01-01T15:37:32.073Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/01/61/d4b89fec821f72385526e1b9d9a3a0385dda4a72b206d28049e2c7cd39b8/gitpython-3.1.45-py3-none-any.whl", hash = "sha256:8908cb2e02fb3b93b7eb0f2827125cb699869470432cc885f019b8fd0fccff77", size = 208168, upload-time = "2025-07-24T03:45:52.517Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl", hash = "sha256:79812ed143d9d25b6d176a10bb511de0f9c67b1fa641d82097b0ab90398a2058", size = 208620, upload-time = "2026-01-01T15:37:30.574Z" },
]
[[package]]
@@ -2652,7 +2668,7 @@ wheels = [
[[package]]
name = "mkdocs-macros-plugin"
-version = "1.4.1"
+version = "1.5.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "hjson" },
@@ -2667,9 +2683,9 @@ dependencies = [
{ name = "termcolor", version = "3.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
{ name = "termcolor", version = "3.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/1b/42/bb2ceed148c77f82b57c6d3b7f584f0f34ababf7a9a8ff85809380d1f400/mkdocs_macros_plugin-1.4.1.tar.gz", hash = "sha256:55a9c93871e3744cdeb0736316783d60830a6d5d97b1132364e6b491607f2332", size = 35094, upload-time = "2025-10-25T12:37:20.689Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/92/15/e6a44839841ebc9c5872fa0e6fad1c3757424e4fe026093b68e9f386d136/mkdocs_macros_plugin-1.5.0.tar.gz", hash = "sha256:12aa45ce7ecb7a445c66b9f649f3dd05e9b92e8af6bc65e4acd91d26f878c01f", size = 37730, upload-time = "2025-11-13T08:08:55.545Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/93/ec/e6e96a7ae8df414f03f43681821234b0d3b86666f7b91f70ab26775a8809/mkdocs_macros_plugin-1.4.1-py3-none-any.whl", hash = "sha256:5a9e483f6056fe7ad0923802affe699233ca468672e20a9640dba237165b3240", size = 40155, upload-time = "2025-10-25T12:37:19.417Z" },
+ { url = "https://files.pythonhosted.org/packages/51/62/9fffba5bb9ed3d31a932ad35038ba9483d59850256ee0fea7f1187173983/mkdocs_macros_plugin-1.5.0-py3-none-any.whl", hash = "sha256:c10fabd812bf50f9170609d0ed518e54f1f0e12c334ac29141723a83c881dd6f", size = 44626, upload-time = "2025-11-13T08:08:53.878Z" },
]
[[package]]
@@ -3238,11 +3254,11 @@ argon2 = [
[[package]]
name = "pyasn1"
-version = "0.6.1"
+version = "0.6.2"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322, upload-time = "2024-09-10T22:41:42.55Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/fe/b6/6e630dff89739fcd427e3f72b3d905ce0acb85a45d4ec3e2678718a3487f/pyasn1-0.6.2.tar.gz", hash = "sha256:9b59a2b25ba7e4f8197db7686c09fb33e658b98339fadb826e9512629017833b", size = 146586, upload-time = "2026-01-16T18:04:18.534Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135, upload-time = "2024-09-11T16:00:36.122Z" },
+ { url = "https://files.pythonhosted.org/packages/44/b5/a96872e5184f354da9c84ae119971a0a4c221fe9b27a4d94bd43f2596727/pyasn1-0.6.2-py3-none-any.whl", hash = "sha256:1eb26d860996a18e9b6ed05e7aae0e9fc21619fcee6af91cca9bad4fbea224bf", size = 83371, upload-time = "2026-01-16T18:04:17.174Z" },
]
[[package]]
@@ -4248,28 +4264,28 @@ wheels = [
[[package]]
name = "ruff"
-version = "0.14.3"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/75/62/50b7727004dfe361104dfbf898c45a9a2fdfad8c72c04ae62900224d6ecf/ruff-0.14.3.tar.gz", hash = "sha256:4ff876d2ab2b161b6de0aa1f5bd714e8e9b4033dc122ee006925fbacc4f62153", size = 5558687, upload-time = "2025-10-31T00:26:26.878Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/ce/8e/0c10ff1ea5d4360ab8bfca4cb2c9d979101a391f3e79d2616c9bf348cd26/ruff-0.14.3-py3-none-linux_armv6l.whl", hash = "sha256:876b21e6c824f519446715c1342b8e60f97f93264012de9d8d10314f8a79c371", size = 12535613, upload-time = "2025-10-31T00:25:44.302Z" },
- { url = "https://files.pythonhosted.org/packages/d3/c8/6724f4634c1daf52409fbf13fefda64aa9c8f81e44727a378b7b73dc590b/ruff-0.14.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b6fd8c79b457bedd2abf2702b9b472147cd860ed7855c73a5247fa55c9117654", size = 12855812, upload-time = "2025-10-31T00:25:47.793Z" },
- { url = "https://files.pythonhosted.org/packages/de/03/db1bce591d55fd5f8a08bb02517fa0b5097b2ccabd4ea1ee29aa72b67d96/ruff-0.14.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:71ff6edca490c308f083156938c0c1a66907151263c4abdcb588602c6e696a14", size = 11944026, upload-time = "2025-10-31T00:25:49.657Z" },
- { url = "https://files.pythonhosted.org/packages/0b/75/4f8dbd48e03272715d12c87dc4fcaaf21b913f0affa5f12a4e9c6f8a0582/ruff-0.14.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:786ee3ce6139772ff9272aaf43296d975c0217ee1b97538a98171bf0d21f87ed", size = 12356818, upload-time = "2025-10-31T00:25:51.949Z" },
- { url = "https://files.pythonhosted.org/packages/ec/9b/506ec5b140c11d44a9a4f284ea7c14ebf6f8b01e6e8917734a3325bff787/ruff-0.14.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cd6291d0061811c52b8e392f946889916757610d45d004e41140d81fb6cd5ddc", size = 12336745, upload-time = "2025-10-31T00:25:54.248Z" },
- { url = "https://files.pythonhosted.org/packages/c7/e1/c560d254048c147f35e7f8131d30bc1f63a008ac61595cf3078a3e93533d/ruff-0.14.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a497ec0c3d2c88561b6d90f9c29f5ae68221ac00d471f306fa21fa4264ce5fcd", size = 13101684, upload-time = "2025-10-31T00:25:56.253Z" },
- { url = "https://files.pythonhosted.org/packages/a5/32/e310133f8af5cd11f8cc30f52522a3ebccc5ea5bff4b492f94faceaca7a8/ruff-0.14.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e231e1be58fc568950a04fbe6887c8e4b85310e7889727e2b81db205c45059eb", size = 14535000, upload-time = "2025-10-31T00:25:58.397Z" },
- { url = "https://files.pythonhosted.org/packages/a2/a1/7b0470a22158c6d8501eabc5e9b6043c99bede40fa1994cadf6b5c2a61c7/ruff-0.14.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:469e35872a09c0e45fecf48dd960bfbce056b5db2d5e6b50eca329b4f853ae20", size = 14156450, upload-time = "2025-10-31T00:26:00.889Z" },
- { url = "https://files.pythonhosted.org/packages/0a/96/24bfd9d1a7f532b560dcee1a87096332e461354d3882124219bcaff65c09/ruff-0.14.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d6bc90307c469cb9d28b7cfad90aaa600b10d67c6e22026869f585e1e8a2db0", size = 13568414, upload-time = "2025-10-31T00:26:03.291Z" },
- { url = "https://files.pythonhosted.org/packages/a7/e7/138b883f0dfe4ad5b76b58bf4ae675f4d2176ac2b24bdd81b4d966b28c61/ruff-0.14.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2f8a0bbcffcfd895df39c9a4ecd59bb80dca03dc43f7fb63e647ed176b741e", size = 13315293, upload-time = "2025-10-31T00:26:05.708Z" },
- { url = "https://files.pythonhosted.org/packages/33/f4/c09bb898be97b2eb18476b7c950df8815ef14cf956074177e9fbd40b7719/ruff-0.14.3-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:678fdd7c7d2d94851597c23ee6336d25f9930b460b55f8598e011b57c74fd8c5", size = 13539444, upload-time = "2025-10-31T00:26:08.09Z" },
- { url = "https://files.pythonhosted.org/packages/9c/aa/b30a1db25fc6128b1dd6ff0741fa4abf969ded161599d07ca7edd0739cc0/ruff-0.14.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1ec1ac071e7e37e0221d2f2dbaf90897a988c531a8592a6a5959f0603a1ecf5e", size = 12252581, upload-time = "2025-10-31T00:26:10.297Z" },
- { url = "https://files.pythonhosted.org/packages/da/13/21096308f384d796ffe3f2960b17054110a9c3828d223ca540c2b7cc670b/ruff-0.14.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:afcdc4b5335ef440d19e7df9e8ae2ad9f749352190e96d481dc501b753f0733e", size = 12307503, upload-time = "2025-10-31T00:26:12.646Z" },
- { url = "https://files.pythonhosted.org/packages/cb/cc/a350bac23f03b7dbcde3c81b154706e80c6f16b06ff1ce28ed07dc7b07b0/ruff-0.14.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:7bfc42f81862749a7136267a343990f865e71fe2f99cf8d2958f684d23ce3dfa", size = 12675457, upload-time = "2025-10-31T00:26:15.044Z" },
- { url = "https://files.pythonhosted.org/packages/cb/76/46346029fa2f2078826bc88ef7167e8c198e58fe3126636e52f77488cbba/ruff-0.14.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a65e448cfd7e9c59fae8cf37f9221585d3354febaad9a07f29158af1528e165f", size = 13403980, upload-time = "2025-10-31T00:26:17.81Z" },
- { url = "https://files.pythonhosted.org/packages/9f/a4/35f1ef68c4e7b236d4a5204e3669efdeefaef21f0ff6a456792b3d8be438/ruff-0.14.3-py3-none-win32.whl", hash = "sha256:f3d91857d023ba93e14ed2d462ab62c3428f9bbf2b4fbac50a03ca66d31991f7", size = 12500045, upload-time = "2025-10-31T00:26:20.503Z" },
- { url = "https://files.pythonhosted.org/packages/03/15/51960ae340823c9859fb60c63301d977308735403e2134e17d1d2858c7fb/ruff-0.14.3-py3-none-win_amd64.whl", hash = "sha256:d7b7006ac0756306db212fd37116cce2bd307e1e109375e1c6c106002df0ae5f", size = 13594005, upload-time = "2025-10-31T00:26:22.533Z" },
- { url = "https://files.pythonhosted.org/packages/b7/73/4de6579bac8e979fca0a77e54dec1f1e011a0d268165eb8a9bc0982a6564/ruff-0.14.3-py3-none-win_arm64.whl", hash = "sha256:26eb477ede6d399d898791d01961e16b86f02bc2486d0d1a7a9bb2379d055dc1", size = 12590017, upload-time = "2025-10-31T00:26:24.52Z" },
+version = "0.14.14"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/2e/06/f71e3a86b2df0dfa2d2f72195941cd09b44f87711cb7fa5193732cb9a5fc/ruff-0.14.14.tar.gz", hash = "sha256:2d0f819c9a90205f3a867dbbd0be083bee9912e170fd7d9704cc8ae45824896b", size = 4515732, upload-time = "2026-01-22T22:30:17.527Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d2/89/20a12e97bc6b9f9f68343952da08a8099c57237aef953a56b82711d55edd/ruff-0.14.14-py3-none-linux_armv6l.whl", hash = "sha256:7cfe36b56e8489dee8fbc777c61959f60ec0f1f11817e8f2415f429552846aed", size = 10467650, upload-time = "2026-01-22T22:30:08.578Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/b1/c5de3fd2d5a831fcae21beda5e3589c0ba67eec8202e992388e4b17a6040/ruff-0.14.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6006a0082336e7920b9573ef8a7f52eec837add1265cc74e04ea8a4368cd704c", size = 10883245, upload-time = "2026-01-22T22:30:04.155Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/7c/3c1db59a10e7490f8f6f8559d1db8636cbb13dccebf18686f4e3c9d7c772/ruff-0.14.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:026c1d25996818f0bf498636686199d9bd0d9d6341c9c2c3b62e2a0198b758de", size = 10231273, upload-time = "2026-01-22T22:30:34.642Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/6e/5e0e0d9674be0f8581d1f5e0f0a04761203affce3232c1a1189d0e3b4dad/ruff-0.14.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f666445819d31210b71e0a6d1c01e24447a20b85458eea25a25fe8142210ae0e", size = 10585753, upload-time = "2026-01-22T22:30:31.781Z" },
+ { url = "https://files.pythonhosted.org/packages/23/09/754ab09f46ff1884d422dc26d59ba18b4e5d355be147721bb2518aa2a014/ruff-0.14.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c0f18b922c6d2ff9a5e6c3ee16259adc513ca775bcf82c67ebab7cbd9da5bc8", size = 10286052, upload-time = "2026-01-22T22:30:24.827Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/cc/e71f88dd2a12afb5f50733851729d6b571a7c3a35bfdb16c3035132675a0/ruff-0.14.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1629e67489c2dea43e8658c3dba659edbfd87361624b4040d1df04c9740ae906", size = 11043637, upload-time = "2026-01-22T22:30:13.239Z" },
+ { url = "https://files.pythonhosted.org/packages/67/b2/397245026352494497dac935d7f00f1468c03a23a0c5db6ad8fc49ca3fb2/ruff-0.14.14-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:27493a2131ea0f899057d49d303e4292b2cae2bb57253c1ed1f256fbcd1da480", size = 12194761, upload-time = "2026-01-22T22:30:22.542Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/06/06ef271459f778323112c51b7587ce85230785cd64e91772034ddb88f200/ruff-0.14.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01ff589aab3f5b539e35db38425da31a57521efd1e4ad1ae08fc34dbe30bd7df", size = 12005701, upload-time = "2026-01-22T22:30:20.499Z" },
+ { url = "https://files.pythonhosted.org/packages/41/d6/99364514541cf811ccc5ac44362f88df66373e9fec1b9d1c4cc830593fe7/ruff-0.14.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc12d74eef0f29f51775f5b755913eb523546b88e2d733e1d701fe65144e89b", size = 11282455, upload-time = "2026-01-22T22:29:59.679Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/71/37daa46f89475f8582b7762ecd2722492df26421714a33e72ccc9a84d7a5/ruff-0.14.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb8481604b7a9e75eff53772496201690ce2687067e038b3cc31aaf16aa0b974", size = 11215882, upload-time = "2026-01-22T22:29:57.032Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/10/a31f86169ec91c0705e618443ee74ede0bdd94da0a57b28e72db68b2dbac/ruff-0.14.14-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:14649acb1cf7b5d2d283ebd2f58d56b75836ed8c6f329664fa91cdea19e76e66", size = 11180549, upload-time = "2026-01-22T22:30:27.175Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/1e/c723f20536b5163adf79bdd10c5f093414293cdf567eed9bdb7b83940f3f/ruff-0.14.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e8058d2145566510790eab4e2fad186002e288dec5e0d343a92fe7b0bc1b3e13", size = 10543416, upload-time = "2026-01-22T22:30:01.964Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/34/8a84cea7e42c2d94ba5bde1d7a4fae164d6318f13f933d92da6d7c2041ff/ruff-0.14.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e651e977a79e4c758eb807f0481d673a67ffe53cfa92209781dfa3a996cf8412", size = 10285491, upload-time = "2026-01-22T22:30:29.51Z" },
+ { url = "https://files.pythonhosted.org/packages/55/ef/b7c5ea0be82518906c978e365e56a77f8de7678c8bb6651ccfbdc178c29f/ruff-0.14.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cc8b22da8d9d6fdd844a68ae937e2a0adf9b16514e9a97cc60355e2d4b219fc3", size = 10733525, upload-time = "2026-01-22T22:30:06.499Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/5b/aaf1dfbcc53a2811f6cc0a1759de24e4b03e02ba8762daabd9b6bd8c59e3/ruff-0.14.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:16bc890fb4cc9781bb05beb5ab4cd51be9e7cb376bf1dd3580512b24eb3fda2b", size = 11315626, upload-time = "2026-01-22T22:30:36.848Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/aa/9f89c719c467dfaf8ad799b9bae0df494513fb21d31a6059cb5870e57e74/ruff-0.14.14-py3-none-win32.whl", hash = "sha256:b530c191970b143375b6a68e6f743800b2b786bbcf03a7965b06c4bf04568167", size = 10502442, upload-time = "2026-01-22T22:30:38.93Z" },
+ { url = "https://files.pythonhosted.org/packages/87/44/90fa543014c45560cae1fffc63ea059fb3575ee6e1cb654562197e5d16fb/ruff-0.14.14-py3-none-win_amd64.whl", hash = "sha256:3dde1435e6b6fe5b66506c1dff67a421d0b7f6488d466f651c07f4cab3bf20fd", size = 11630486, upload-time = "2026-01-22T22:30:10.852Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/6a/40fee331a52339926a92e17ae748827270b288a35ef4a15c9c8f2ec54715/ruff-0.14.14-py3-none-win_arm64.whl", hash = "sha256:56e6981a98b13a32236a72a8da421d7839221fa308b223b9283312312e5ac76c", size = 10920448, upload-time = "2026-01-22T22:30:15.417Z" },
]
[[package]]
@@ -4415,15 +4431,15 @@ wheels = [
[[package]]
name = "sqlmodel"
-version = "0.0.27"
+version = "0.0.31"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pydantic" },
{ name = "sqlalchemy" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/90/5a/693d90866233e837d182da76082a6d4c2303f54d3aaaa5c78e1238c5d863/sqlmodel-0.0.27.tar.gz", hash = "sha256:ad1227f2014a03905aef32e21428640848ac09ff793047744a73dfdd077ff620", size = 118053, upload-time = "2025-10-08T16:39:11.938Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/56/b8/e7cd6def4a773f25d6e29ffce63ccbfd6cf9488b804ab6fb9b80d334b39d/sqlmodel-0.0.31.tar.gz", hash = "sha256:2d41a8a9ee05e40736e2f9db8ea28cbfe9b5d4e5a18dd139e80605025e0c516c", size = 94952, upload-time = "2025-12-28T12:35:01.436Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/8c/92/c35e036151fe53822893979f8a13e6f235ae8191f4164a79ae60a95d66aa/sqlmodel-0.0.27-py3-none-any.whl", hash = "sha256:667fe10aa8ff5438134668228dc7d7a08306f4c5c4c7e6ad3ad68defa0e7aa49", size = 29131, upload-time = "2025-10-08T16:39:10.917Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/72/5aa5be921800f6418a949a73c9bb7054890881143e6bc604a93d228a95a3/sqlmodel-0.0.31-py3-none-any.whl", hash = "sha256:6d946d56cac4c2db296ba1541357cee2e795d68174e2043cd138b916794b1513", size = 27093, upload-time = "2025-12-28T12:35:00.108Z" },
]
[[package]]
@@ -4737,7 +4753,7 @@ wheels = [
[[package]]
name = "typer"
-version = "0.16.0"
+version = "0.21.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
@@ -4746,9 +4762,9 @@ dependencies = [
{ name = "shellingham" },
{ name = "typing-extensions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/c5/8c/7d682431efca5fd290017663ea4588bf6f2c6aad085c7f108c5dbc316e70/typer-0.16.0.tar.gz", hash = "sha256:af377ffaee1dbe37ae9440cb4e8f11686ea5ce4e9bae01b84ae7c63b87f1dd3b", size = 102625, upload-time = "2025-05-26T14:30:31.824Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/36/bf/8825b5929afd84d0dabd606c67cd57b8388cb3ec385f7ef19c5cc2202069/typer-0.21.1.tar.gz", hash = "sha256:ea835607cd752343b6b2b7ce676893e5a0324082268b48f27aa058bdb7d2145d", size = 110371, upload-time = "2026-01-06T11:21:10.989Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/76/42/3efaf858001d2c2913de7f354563e3a3a2f0decae3efe98427125a8f441e/typer-0.16.0-py3-none-any.whl", hash = "sha256:1f79bed11d4d02d4310e3c1b7ba594183bcedb0ac73b27a9e5f28f6fb5b98855", size = 46317, upload-time = "2025-05-26T14:30:30.523Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/1d/d9257dd49ff2ca23ea5f132edf1281a0c4f9de8a762b9ae399b670a59235/typer-0.21.1-py3-none-any.whl", hash = "sha256:7985e89081c636b88d172c2ee0cfe33c253160994d47bdfdc302defd7d1f1d01", size = 47381, upload-time = "2026-01-06T11:21:09.824Z" },
]
[[package]]